字符串拼接
1、直接使用+
号
2、使用bytes.Buffer
3、使用strings.Builder
func BenchmarkStringConcat1(b *testing.B) {
s := ""
for i := 0; i < b.N; i++ {
s += "hello world"
}
}
func BenchmarkStringConcat2(b *testing.B) {
var buf bytes.Buffer
for i := 0; i < b.N; i++ {
buf.WriteString("hello world")
}
_ = buf.String()
}
func BenchmarkStringConcat3(b *testing.B) {
var sb strings.Builder
for i := 0; i < b.N; i++ {
sb.WriteString("hello world")
}
_ = sb.String()
}
> go test -bench=. -benchtime=100000x -benchmem
goos: windows
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
BenchmarkStringConcat1-8 100000 73067 ns/op 554013 B/op 1 allocs/op
BenchmarkStringConcat2-8 100000 19.38 ns/op 53 B/op 0 allocs/op
BenchmarkStringConcat3-8 100000 9.759 ns/op 52 B/op 0 allocs/op
PASS
ok test 7.358s
从性能来看,strings.Builder
是最好的,而直接使用+
号性能是最差的。
当使用连接符 +
拼接两个字符串时,会生成一个新的字符串并开辟新的内存空间,空间大小等于两个字符串之和。刚才的基准测试中,循环不断拼接新的字符串,这样就会不断申请内存空间,以此类推,性能就会越来越差。在字符串密集拼接场景中,使用 +
会严重降低性能。
strings.Builder
与 bytes.Buffer
两者内存分配差不多,性能方面,前者更优。两者底层都是一个 []byte
,但是 bytes.Buffer
转换字符串时重新申请了内存空间用来存放, 而 strings.Builder
使用 unsafe 包的能力直接将底层的 []byte
定义为字符串返回。
// bytes.Buffer
func (b *Buffer) String() string {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
return string(b.buf[b.off:])
}
// strings.Builder
func (b *Builder) String() string {
return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))
}
// String returns a string value whose underlying bytes
// start at ptr and whose length is len.
//
// The len argument must be of integer type or an untyped constant.
// A constant len argument must be non-negative and representable by a value of type int;
// if it is an untyped constant it is given type int.
// At run time, if len is negative, or if ptr is nil and len is not zero,
// a run-time panic occurs.
//
// Since Go strings are immutable, the bytes passed to String
// must not be modified afterwards.
func String(ptr *byte, len IntegerType) string
字符串与切片转换
在golang中,字符串和切片的运行时底层数据结构非常相似,所以使用 unsafe
包可以绕过类型系统来实现类型转换。
type StringHeader struct {
Data uintptr
Len int
}
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
1、字符串转换成切片
func BenchmarkStringToByte1(b *testing.B) {
s := "hello world"
for i := 0; i < b.N; i++ {
s2b1(s)
}
}
func s2b1(s string) []byte {
return []byte(s)
}
func BenchmarkStringToByte2(b *testing.B) {
s := "hello world"
for i := 0; i < b.N; i++ {
s2b2(s)
}
}
func s2b2(s string) (b []byte) {
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Len = sh.Len
bh.Cap = sh.Len
return
}
> go test -bench=BenchmarkStringToByte -benchtime=10000000x -benchmem
goos: windows
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
BenchmarkStringToByte1-8 10000000 4.904 ns/op 0 B/op 0 allocs/op
BenchmarkStringToByte2-8 10000000 0.2929 ns/op 0 B/op 0 allocs/op
2、切片转换成字符串
func BenchmarkByteToString1(b *testing.B) {
bb := []byte(`hello world`)
for i := 0; i < b.N; i++ {
b2s1(bb)
}
}
func b2s1(b []byte) string {
return string(b)
}
func BenchmarkByteToString2(b *testing.B) {
bb := []byte(`hello world`)
for i := 0; i < b.N; i++ {
b2s2(bb)
}
}
func b2s2(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
> go test -bench=BenchmarkByteToString -benchtime=10000000x -benchmem
goos: windows
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
BenchmarkByteToString1-8 10000000 6.078 ns/op 0 B/op 0 allocs/op
BenchmarkByteToString2-8 10000000 0.2929 ns/op 0 B/op 0 allocs/op
性能上提升还是很明显的