一、常用拼接方法
字符串拼接在日常开发中是很常见的需求,目前有两种普遍做法:
一种是直接用 += 来拼接
s1 := "hi "
s2 := "sheng"
s3 := s1 + s2 // s3 == "hi sheng"
s1 += s2 // s1 == "hi sheng"
这是最常用也是最简单直观的方法,不过简单是有代价的,golang的字符串是不可变类型,也就是说每一次对字符串的“原地”修改都会重新生成一个string,再把数据复制进去,这样一来将会产生很可观的性能开销,稍后的性能测试中将会看到这一点。
func TestS(t *testing.T) {
s := "hello"
t.Logf("%p", &s) //0xc000092350
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))//&{18098388 5}
t.Log(stringHeader.Data)
s += "world"
t.Logf("%p", &s) //0xc000092350
stringHeader = (*reflect.StringHeader)(unsafe.Pointer(&s))//&{824634335664 10}
t.Log(stringHeader.Data)
}
输出:
0xc000092350
&{18098388 5}
0xc000092350
&{824634335664 10}
二、strings.Builder
func TestB(t *testing.T) {
var b strings.Builder
b.Grow(100)
b.WriteString("hello")
t.Log(b)
b.WriteString("world")
t.Log(b)
t.Log(b.String())
}
输出:
{0xc000036730 [104 101 108 108 111]}
{0xc00009cf30 [104 101 108 108 111 119 111 114 108 100]}
helloworld
源码在 src/strings/builder.go
主要由 一个 Builder 结构体
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
然write的时候,写入到 buf这个slice里面,源码如下:
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}
三、有啥区别?
表列 A | += | strings.Builder |
---|---|---|
扩容方式 | 每次赋值都重新分配内存 | 同slice append,不够用的时候,扩容1倍 |
时间复杂度 | n | log(n) |
以128次拼接为例 | 128次扩容 | 7次扩容 2的7次方 |
我们在 test文件里,写如下代码:
func BenchmarkAdd(b *testing.B) {
s := ""
for i := 0; i < b.N; i++ {
s += "a"
}
}
func BenchmarkBuild(b *testing.B) {
var s strings.Builder
for i := 0; i < b.N; i++ {
s.WriteString("a")
}
}
然后执行
go test -bench=. -benchmem
输出:
goos: darwin
goarch: amd64
pkg: github.com/hisheng/dataStructure/strings
BenchmarkAdd-8 1000000 71429 ns/op 503995 B/op 1 allocs/op
BenchmarkBuild-8 262080333 4.71 ns/op 6 B/op 0 allocs/op
PASS
ok github.com/hisheng/dataStructure/strings 73.841s
会发现在for循环下的多次扩容,用builder效率高非常多。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。