一、常用拼接方法

字符串拼接在日常开发中是很常见的需求,目前有两种普遍做法:
一种是直接用 += 来拼接

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倍
时间复杂度nlog(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效率高非常多。

谢谢您的观看,欢迎关注我的公众号。

image.png


海生
104 声望32 粉丝

与黑夜里,追求那一抹萤火。