TOC

Golang strings.Builder

https://github.com/golang/go/blob/master/src/strings/builder.go

主要的作用是频繁拼接字符串,相比字符串拼接操作符(+)或者 fmt.SPrintf 效率更高。

实例

package main

import (
    "fmt"
    "strings"
)

func main() {
    var builder strings.Builder
    builder.WriteString("Hello")
    builder.WriteString(", ")
    builder.WriteString("World!")
    result := builder.String()
    fmt.Println(result)
}

方法

  • func (b *Builder) String() string
  • func (b *Builder) Len() int { return len(b.buf)
  • func (b *Builder) Cap() int { return cap(b.buf)
  • func (b *Builder) Reset() 内容重置
  • func (b *Builder) Grow(n int) 预分配空间,正确使用可以提升程序执行效率
  • 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 的一个封装。

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

func (b *Builder) copyCheck() {
    if b.addr == nil {
        // This hack works around a failing of Go's escape analysis
        // that was causing b to escape and be heap allocated.
        // See issue 23382.
        // TODO: once issue 7921 is fixed, this should be reverted to
        // just "b.addr = b".
        b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
    } else if b.addr != b {
        panic("strings: illegal use of non-zero Builder copied by value")
    }
}

// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
    b.copyCheck()
    b.buf = append(b.buf, s...)
    return len(s), nil
}
  • addr 指向自己的指针,不知道干什么用
  • buf 是 buffer,缓冲区,用于存储 build 出来的字符串
  • 通过缓冲区做存储,避免频繁创建字符串对象,减少内存分配和垃圾回收

性能测试

BenchmarkStrPlus-20             100000000               11.11 ns/op            0 B/op          0 allocs/op
BenchmarkSprintf-20             12129922               101.5 ns/op            48 B/op          3 allocs/op
BenchmarkStrJoin-20             45257742                24.47 ns/op           16 B/op          1 allocs/op
BenchmarkStrBuilder-20          38868284                36.31 ns/op           24 B/op          2 allocs/op
BenchmarkByteBuf-20             22016934                53.34 ns/op           80 B/op          2 allocs/op
BenchmarkByteSlice-20           31495318                36.13 ns/op           24 B/op          2 allocs/op
BenchmarkByteSlice2-20          39563612                32.18 ns/op           24 B/op          2 allocs/op

BenchmarkStrPlusBatch-20         7331364               170.0 ns/op           112 B/op          6 allocs/op
BenchmarkSprintfBatch-20         1481492               823.2 ns/op           328 B/op         20 allocs/op
BenchmarkStrJoinBatch-20        19598844                59.78 ns/op           32 B/op          1 allocs/op
BenchmarkStrBuilderBatch-20     13560961                87.39 ns/op           56 B/op          3 allocs/op
BenchmarkByteBufBatch-20        10693280                97.61 ns/op           96 B/op          2 allocs/op
BenchmarkByteSliceBatch-20      12115166                95.27 ns/op           56 B/op          3 allocs/op
BenchmarkByteSlice2Batch-20     13136418                90.53 ns/op           56 B/op          3 allocs/op

fmt.Sprintf 用来做字符串拼接的话,性能总是最差。
此外,如果只是简单的两个字符串相加,直接加号拼接性能最好。
否则就根据情况,选用其他方案比较好。