Go语言字符串拼接性能对比与优化指南
# 简介
在Go语言中,常见的字符串拼接方式有以下6种:
- +号拼接:最简单的拼接方式
- fmt.Sprintf:格式化拼接
- strings.Builder:专门优化的字符串构建器
- bytes.Buffer:字节缓冲区
- []byte转换:字节切片转换
- 预分配[]byte:预先分配足够空间的字节切片
本文主要是针对上面各种方式进行基准测试,帮助我们在以后的编码过程中选择最优的方式。
# 基准测试大比拼
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func plusConcat(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
func sprintfConcat(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s = fmt.Sprintf("%s%s", s, str)
}
return s
}
func builderConcat(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
func bufferConcat(n int, s string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(s)
}
return buf.String()
}
func byteConcat(n int, str string) string {
buf := make([]byte, 0)
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
return string(buf)
}
func preByteConcat(n int, str string) string {
buf := make([]byte, 0, n*len(str))
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
return string(buf)
}
func benchmark(b *testing.B, f func(int, string) string) {
var str = randomString(10)
for i := 0; i < b.N; i++ {
f(10000, str)
}
}
func BenchmarkPlusConcat(b *testing.B) { benchmark(b, plusConcat) }
func BenchmarkSprintfConcat(b *testing.B) { benchmark(b, sprintfConcat) }
func BenchmarkBuilderConcat(b *testing.B) { benchmark(b, builderConcat) }
func BenchmarkBufferConcat(b *testing.B) { benchmark(b, bufferConcat) }
func BenchmarkByteConcat(b *testing.B) { benchmark(b, byteConcat) }
func BenchmarkPreByteConcat(b *testing.B) { benchmark(b, preByteConcat) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
结果如下:
go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkPlusConcat-12 36 32238928 ns/op 530999857 B/op 10045 allocs/op
BenchmarkSprintfConcat-12 20 56848854 ns/op 833393446 B/op 34184 allocs/op
BenchmarkBuilderConcat-12 23143 51218 ns/op 514804 B/op 23 allocs/op
BenchmarkBufferConcat-12 29152 40997 ns/op 368578 B/op 13 allocs/op
BenchmarkByteConcat-12 19549 60984 ns/op 621301 B/op 24 allocs/op
BenchmarkPreByteConcat-12 50913 24879 ns/op 212994 B/op 2 allocs/op
PASS
ok main/demo 10.263s
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 性能分析
+号和fmt.Sprintf性能差的原因:
- 每次操作都会创建新的字符串
- 产生大量内存分配和垃圾回收压力
strings.Builder/bytes.Buffer表现好的原因
- 内部使用[]byte缓冲区
- 按需扩容,减少内存分配次数
预分配[]byte最佳的原因
- 一次性分配足够内存
- 完全避免了扩容带来的性能损耗
# 总结
通过测试我们发现:
- 预分配[]byte性能最佳,适合高性能场景
- strings.Builder是通用场景的最佳选择
- +号和fmt.Sprintf在循环拼接中性能极差
在实际开发中,应根据场景选择合适的方法,在代码可读性和性能之间取得平衡。
上次更新: 2025/06/14, 16:16:07