登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> SoftHub关联区 >> 主题: Golang 中 strings.builder 的 7 个要点     [回主站]     [分站链接]
Golang 中 strings.builder 的 7 个要点
clq
浏览(221) - 2018-12-23 21:57:17 发表 编辑

关键字: golang

Golang 中 strings.builder 的 7 个要点

https://studygolang.com/articles/12796?fr=sidebar

 Golang 中 strings.builder 的 7 个要点
alfred-zhong · 2018-04-14 10:58:21 · 1969 次点击 · 预计阅读时间 5 分钟 · 9分钟之前 开始浏览   
这是一个创建于 2018-04-14 10:58:21 的文章,其中的信息可能已经有所发展或是发生改变。

自从 Go 1.10 发布的一个月以来,我多少使用了一下 strings.Builder,略有心得。你也许知道它,特别是你了解 bytes.Buffer 的话。所以我在此分享一下我的心得,并希望能对你有所帮助。
1. 4 类写入(write)方法

与 bytes.Buffer 类似,strings.Builder 也支持 4 类方法将数据写入 builder 中。

func (b *Builder) Write(p []byte) (int, error)
func (b *Builder) WriteByte(c byte) error
func (b *Builder) WriteRune(r rune) (int, error)
func (b *Builder) WriteString(s string) (int, error)

有了它们,用户可以根据输入数据的不同类型(byte 数组,byte, rune 或者 string),选择对应的写入方法。

four-forms-of-writing-methods
2. 字符串的存储原理

根据用法说明,我们通过调用 string.Builder 的写入方法来写入内容,然后通过调用 String() 方法来获取拼接的字符串。那么 string.Builder 是如何组织这些内容的呢?

通过 slice

string.Builder 通过使用一个内部的 slice 来存储数据片段。当开发者调用写入方法的时候,数据实际上是被追加(append)到了其内部的 slice 上。

slice-store-data
3. 高效地使用 strings.Builder

根据上面第 2 点可以知道,strings.Builder 是通过其内部的 slice 来储存内容的。当你调用写入方法的时候,新的字节数据就被追加到 slice 上。如果达到了 slice 的容量(capacity)限制,一个新的 slice 就会被分配,然后老的 slice 上的内容会被拷贝到新的 slice 上。当 slice 长度很大时,这个操作就会很消耗资源甚至引起 内存问题。我们需要避免这一情况。

关于 slice,Go 语言提供了 make([]TypeOfSlice, length, capacity) 方法在初始化的时候预定义它的容量。这就避免了因达到最大容量而引起扩容。

strings.Builder 同样也提供了 Grow() 来支持预定义容量。当我们可以预定义我们需要使用的容量时,strings.Builder 就能避免扩容而创建新的 slice 了。

func (b *Builder) Grow(n int)

当调用 Grow() 时,我们必须定义要扩容的字节数(n)。 Grow() 方法保证了其内部的 slice 一定能够写入 n 个字节。只有当 slice 空余空间不足以写入 n 个字节时,扩容才有可能发生。举个例子:

    builder 内部 slice 容量为 10。
    builder 内部 slice 长度为 5。
    当我们调用 Grow(3) => 扩容操作并不会发生。因为当前的空余空间为 5,足以提供 3 个字节的写入。
    当我们调用 Grow(7) => 扩容操作发生。因为当前的空余空间为 5,已不足以提供 7 个字节的写入。

关于上面的情形,如果这时我们调用 Grow(7),则扩容之后的实际容量是多少?

17 还是 12?

实际上,是 27。strings.Builder 的 Grow() 方法是通过 current_capacity * 2 + n (n 就是你想要扩充的容量)的方式来对内部的 slice 进行扩容的。所以说最后的容量是 10*2+7 = 27。

当你预定义 strings.Builder 容量的时候还要注意一点。调用 WriteRune() 和 WriteString() 时,rune 和 string 的字符可能不止 1 个字节。因为,你懂的,UTF-8 的原因。
4. String()

和 bytes.Buffer 一样,strings.Builder 也支持使用 String() 来获取最终的字符串结果。为了节省内存分配,它通过使用指针技术将内部的 buffer bytes 转换为字符串。所以 String() 方法在转换的时候节省了时间和空间。

*(*string)(unsafe.Pointer(&bytes))

5. 不要拷贝

do-not-copy

strings.Builder 不推荐被拷贝。当你试图拷贝 strings.Builder 并写入的时候,你的程序就会崩溃。

var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
b2.WriteString("DEF")
// illegal use of non-zero Builder copied by value

你已经知道,strings.Builder 内部通过 slice 来保存和管理内容。slice 内部则是通过一个指针指向实际保存内容的数组。

slice-internally

当我们拷贝了 builder 以后,同样也拷贝了其 slice 的指针。但是它仍然指向同一个旧的数组。当你对源 builder 或者拷贝后的 builder 写入的时候,问题就产生了。另一个 builder 指向的数组内容也被改变了。这就是为什么 strings.Builder 不允许拷贝的原因。

copy-and-write

对于一个未写入任何东西的空内容 builder 则是个例外。我们可以拷贝空内容的 builder 而不报错。

var b1 strings.Builder
b2 := b1
b2.WriteString("DEF")
b1.WriteString("ABC")

// b1 = ABC, b2 = DEF

strings.Builder 会在以下方法中检测拷贝操作:

Grow(n int)
Write(p []byte)
WriteRune(r rune)
WriteString(s string)

所以,拷贝并使用下列这些方法是允许的:

// Reset()
// Len()
// String()

var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
fmt.Println(b2.Len())    // 3
fmt.Println(b2.String()) // ABC
b2.Reset()
b2.WriteString("DEF")
fmt.Println(b2.String()) // DEF

6. 并行支持

和 bytes.Buffer 一样,strings.Builder 也不支持并行的读或者写。所以我们们要稍加注意。

可以试一下,通过同时给 strings.Builder 添加 1000 个字符:

package main

import (
    "fmt"
    "strings"
    "sync"
)

func main() {
    var b strings.Builder
    n := 0
    var wait sync.WaitGroup
    for n < 1000 {
        wait.Add(1)
        go func() {
            b.WriteString("1")
            n++
            wait.Done()
        }()
    }
    wait.Wait()
    fmt.Println(len(b.String()))
}

通过运行,你会得到不同长度的结果。但它们都不到 1000。
7. io.Writer 接口

strings.Builder 通过 Write(p []byte) (n int, err error) 方法实现了 io.Writer 接口。所以,我们多了很多使用它的情形:

    io.Copy(dst Writer, src Reader) (written int64, err error)
    bufio.NewWriter(w io.Writer) *Writer
    fmt.Fprint(w io.Writer, a …interface{}) (n int, err error)
    func (r *http.Request) Write(w io.Writer) error
    其他使用 io.Writer 的库

io-writer

via: https://medium.com/@thuc/8-notes-about-strings-builder-in-golang-65260daae6e9

作者:Thuc Le  译者:alfred-zhong  校对:rxcai

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽



总数:0 页次:1/0 首页 尾页  
总数:0 页次:1/0 首页 尾页  


所在合集/目录
golang新技术 更多



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1