strings.Builder 源码分析
Posted Golang语言社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了strings.Builder 源码分析相关的知识,希望对你有一定的参考价值。
Go 1.10开始,引入了期盼已久的strings.Builder,Go 的作者是不是看到雨痕大大的优化文章搞的这个呢?
strings.Builder 和 bytes.Buffer 接口设计上基本一致
底层实现
String() 方法有门道
不允许复制
线程不安全
参考文献
strings.Builder 和 bytes.Buffer 接口设计上基本一致
支持的方法是 bytes.Buffer 的子集,仔细看了一下,它实现了io.Writer
接口,而 bytes.Buffer 实现了io.Reader
和io.Writer
两个接口。
type Builder func (b *Builder) Grow(n int) func (b *Builder) Len() int func (b *Builder) Reset() func (b *Builder) String() string 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)
1type Writer interface {
2 Write(p []byte) (n int, err error)
3}
底层实现
1type Builder struct {
2 addr *Builder // of receiver, to detect copies by value
3 buf []byte
4}
它底层还是用[]byte
保存数据的,这和 bytes.Buffer 是一致的。
如果写入数据,就是在[]byte
后面追加内容:
1func (b *Builder) Write(p []byte) (int, error) {
2 b.copyCheck()
3 b.buf = append(b.buf, p...)
4 return len(p), nil
5}
追加内容也有讲究,因为底层是 slice,追加数据时有可能引起 slice 扩容。一般的优化方案是为 slice 初始化合理的空间,避免多次扩容复制。Builder 也提供了预分配内存的方法:
1func (b *Builder) grow(n int) {
2 buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
3 copy(buf, b.buf)
4 b.buf = buf
5}
6
7func (b *Builder) Grow(n int) {
8 b.copyCheck()
9 if n < 0 {
10 panic("strings.Builder.Grow: negative count")
11 }
12 if cap(b.buf)-len(b.buf) < n {
13 b.grow(n)
14 }
15}
注意扩容的容量和 slice 直接扩容两倍的方式略有不同,它是2*cap(b.buf)+n
,之前容量的两倍加n。
如果容量是10,长度是5,调用
Grow(3)
结果是什么?当前容量足够使用,没有任何操作;如果容量是10,长度是5,调用
Grow(7)
结果是什么?剩余空间是5,不满足7个扩容空间,底层需要扩容。扩容的时候按照之前容量的两倍再加n的新容量扩容,结果是210+7=27*。
String() 方法有门道
1func (b *Builder) String() string {
2 return *(*string)(unsafe.Pointer(&b.buf))
3}
从 ptype 输出的结构来看,string 可看做 [2]uintptr,而 [ ]byte 则是 [3]uintptr,这便于我们编写代码,无需额外定义结构类型。如此,str2bytes 只需构建 [3]uintptr{ptr, len, len},而 bytes2str 更简单,直接转换指针类型,忽略掉 cap 即可。
详细可以参考雨痕的【Go性能优化技巧 1/10】。
不允许复制
还是再看一下 Builder 的底层数据,它还有个字段addr
,是一个指向 Builder 的指针。
1type Builder struct {
2 addr *Builder // of receiver, to detect copies by value
3 buf []byte
4}
默认情况是它会指向自己:
1b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
1func (b *Builder) copyCheck() {
2 if b.addr == nil {
3 // This hack works around a failing of Go's escape analysis
4 // that was causing b to escape and be heap allocated.
5 // See issue 23382.
6 // TODO: once issue 7921 is fixed, this should be reverted to
7 // just "b.addr = b".
8 b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
9 } else if b.addr != b {
10 panic("strings: illegal use of non-zero Builder copied by value")
11 }
12}
copyCheck
用来保证复制后不允许修改的逻辑。仔细看下源码,如果addr
是空,也就是没有数据的时候是可以被复制后修改的,一旦那边有数据了,就不能这么搞了。在Grow
、Write
、WriteByte
、WriteString
、WriteRune
这五个函数里都有这个检查逻辑。
线程不安全
这个包并不是线程安全的,整个例子看看:
1package mainimport (
2 "fmt"
3 "strings"
4 "sync"
5 "sync/atomic")func main() {
6 var b strings.Builder
7 var n int32
8 var wait sync.WaitGroup
9 for i := 0; i < 1000; i++ {
10 wait.Add(1)
11 go func() {
12 atomic.AddInt32(&n, 1)
13 b.WriteString("1")
14 wait.Done()
15 }()
16 }
17 wait.Wait()
18 fmt.Println(len(b.String()), n)}
结果是902 1000
,并不都是1000。如果想保证线程安全,需要在WriteString
的时候加锁。
1package mainimport (
2 "fmt"
3 "strings"
4 "sync"
5 "sync/atomic")func main() {
6 var b strings.Builder
7 var n int32
8 var wait sync.WaitGroup
9 var lock sync.Mutex
10 for i := 0; i < 1000; i++ {
11 wait.Add(1)
12
13 go func() {
14 atomic.AddInt32(&n, 1)
15
16 lock.Lock()
17 b.WriteString("1")
18 lock.Unlock()
19 wait.Done()
20 }()
21 }
22 wait.Wait()
23
24 fmt.Println(len(b.String()), n)}
参考文献
【1】7 notes about strings.builder in Golang
原文链接:strings.Builder 源码分析
干货来了!!!为了让更多的小伙伴喜欢Golang、加入Golang之中来,Golang语言社区发起人彬哥联合业界大牛共同推出了Go语言实战系列课程,目前已在网易云课堂上线,希望有兴趣的朋友们多多分享和支持!如果对视频好的建议可加群:713828896,提意见就送社区免费送论坛VIP!
长按扫描下方二维码或点击阅读原文,即可了解课程详情!
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。
Golang语言社区
ID:Golangweb
游戏服务器架构丨分布式技术丨大数据丨游戏算法学习
以上是关于strings.Builder 源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段
Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段
Android 事件分发事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )(代码片段