go string和Java string一样,都是不可变的,也就是每次string的更新操作都会产生一个新的对象,因此如果在字符串频繁更新场景中,直接操作string性能较低。使用strings.Builder,避免频繁创建字符串对象,进而提高性能。
string.Builer结构体源码及使用示例:
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
builder := strings.Builder{}
builder.WriteString("builder")
fmt.Printf("%v\n",builder.String())
strings.Builer逻辑很简单,底层就是byte切片用于存储数据,外加add指针用于避免Builer使用后的赋值操作,这是一个比较hack的方式,源码如下:
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
func (b *Builder) copyCheck() {
if b.addr == nil {
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
// 如果发生了赋值操作,那么第二个builder变量在使用后会报panic
panic("strings: illegal use of non-zero Builder copied by value")
}
}
注意:copyCheck方法只在写方法中被调用,如果Builer赋值给另外一个Builer变量,只是读操作,并不会触发panic,不过虽然读操作不会触发panic,但也不建议针对Builer的赋值操作,赋值操作除了直接赋值外,还包括函数返回。
针对Builer的WriteString、WriteByte操作,源码如下:
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}
其中只是简单调用append将字符串写入到byte切片中,切片的扩容由底层的append方法来控制。append方法内部会调用方法glowSlice进行切片扩容,扩容策略是:在容量小于1024的时候每次乘以2,之后在小于cap的时候每次加1/4,一直到超过为止。详情见:jiajunhuang.com/articles/20…。
bytes.Buffer和strings.Builer底层实现基本也类似,差别就是在toString时,前者是会新创建内存转成string变量返回,后者是直接将data数组 直接强转为string返回。