前言

Go

string类型

Gostring
// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string
string8
string
type stringStruct struct {
    str unsafe.Pointer
    len int
}
stringStructslicestrlenslice
//go:nosplit
func gostringnocopy(str *byte) string {
 ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
 s := *(*string)(unsafe.Pointer(&ss))
 return s
}
bytestringbyte
图片
stringbyteGostringGostring
stringstringStructstrgc
string

字符串拼接的6种方式及原理

原生拼接方式"+"

Go+
var s string
s += "asong"
s += "真帅"
+
fmt.Sprintf
Gofmt.Sprintf
str := "asong"
str = fmt.Sprintf("%s%s", str, str)
fmt.Sprintf

Strings.builder

Gostringsstrings.BuilderwriteString
var builder strings.Builder
builder.WriteString("asong")
builder.String()
strings.builder
type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte // 1
}
addrcopycheckbufbytewriteString()buf
func (b *Builder) WriteString(s string) (int, error) {
 b.copyCheck()
 b.buf = append(b.buf, s...)
 return len(s), nil
}
String[]]bytestring
func (b *Builder) String() string {
 return *(*string)(unsafe.Pointer(&b.buf))
}

bytes.Buffer

stringbyteGobytes.Bufferbytes.Bufferbytebyte
buf := new(bytes.Buffer)
buf.WriteString("asong")
buf.String()
bytes.buffer[]byte
type Buffer struct {
 buf      []byte // contents are the bytes buf[off : len(buf)]
 off      int    // read at &buf[off], write at &buf[len(buf)]
 lastRead readOp // last read operation, so that Unread* can work correctly.
}
bytes.BufferBufferBufferoffcapWriteString
func (b *Buffer) WriteString(s string) (n int, err error) {
 b.lastRead = opInvalid
 m, ok := b.tryGrowByReslice(len(s))
 if !ok {
  m = b.grow(len(s))
 }
 return copy(b.buf[m:], s), nil
}
slicecopycopy
[]bytestring
func (b *Buffer) String() string {
 if b == nil {
  // Special case, useful in debugging.
  return "<nil>"
 }
 return string(b.buf[b.off:])
}

strings.join

Strings.joinstring
baseSlice := []string{"asong", "真帅"}
strings.Join(baseSlice, "")
strings.joinstrings.builder
func Join(elems []string, sep string) string {
 switch len(elems) {
 case 0:
  return ""
 case 1:
  return elems[0]
 }
 n := len(sep) * (len(elems) - 1)
 for i := 0; i < len(elems); i++ {
  n += len(elems[i])
 }

 var b Builder
 b.Grow(n)
 b.WriteString(elems[0])
 for _, s := range elems[1:] {
  b.WriteString(sep)
  b.WriteString(s)
 }
 return b.String()
}
joinb.Grow(n)
append
stringbyteappend
buf := make([]byte, 0)
base = "asong"
buf = append(buf, base...)
string(base)
[]bytestring

Benchmark对比

GoBenchmark
  • 少量字符串拼接

  • 大量字符串拼接

github

我们先定义一个基础字符串:

var base  = "123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASFGHJKLZXCVBNM"

少量字符串拼接的测试我们就采用拼接一次的方式验证,base拼接base,因此得出benckmark结果:

goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/string_join/once
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSumString-16           21338802                49.19 ns/op          128 B/op          1 allocs/op
BenchmarkSprintfString-16        7887808               140.5 ns/op           160 B/op          3 allocs/op
BenchmarkBuilderString-16       27084855                41.39 ns/op          128 B/op          1 allocs/op
BenchmarkBytesBuffString-16      9546277               126.0 ns/op           384 B/op          3 allocs/op
BenchmarkJoinstring-16          24617538                48.21 ns/op          128 B/op          1 allocs/op
BenchmarkByteSliceString-16     10347416               112.7 ns/op           320 B/op          3 allocs/op
PASS
ok      asong.cloud/Golang_Dream/code_demo/string_join/once     8.412s

大量字符串拼接的测试我们先构建一个长度为200的字符串切片:

var baseSlice []string
for i := 0; i < 200; i++ {
  baseSlice = append(baseSlice, base)
}
benchmark
goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/string_join/muliti
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSumString-16                       7396            163612 ns/op         1277713 B/op        199 allocs/op
BenchmarkSprintfString-16                   5946            202230 ns/op         1288552 B/op        600 allocs/op
BenchmarkBuilderString-16                 262525              4638 ns/op           40960 B/op          1 allocs/op
BenchmarkBytesBufferString-16             183492              6568 ns/op           44736 B/op          9 allocs/op
BenchmarkJoinstring-16                    398923              3035 ns/op           12288 B/op          1 allocs/op
BenchmarkByteSliceString-16               144554              8205 ns/op           60736 B/op         15 allocs/op
PASS
ok      asong.cloud/Golang_Dream/code_demo/string_join/muliti   10.699s

结论

benchmark
++fmt.Sprintfstrings.BuilderGostrings.builderstrings.builderGrowstrings.joingrowstrings.builderbytes.Bufferstrings.builderbytes.Bufferstrings.buidler[]byte

同步最后分析的结论:

strings.buildergrowstrings.joinstrings.builder+strings.builder

综合对比性能排序:

strings.join` ≈ `strings.builder` > `bytes.buffer` > `[]byte`转换`string` > "+" > `fmt.sprintf

总结

6benckmarkstrings.builder+

- END -

本文转载自阿松的公众号,喜欢文章的欢迎关注后续的持续更新。