Go 字符串连接方式及性能对比

chihqiang chihqiang 2026-07-13 约 12 分钟 4674 字

Go 字符串连接方式及性能对比

在 Go 语言中,有多种方式可以连接字符串,本文介绍常用的几种方式并进行性能对比。

1. 使用 + 号连接

最简单直接的方式,但会产生新的字符串对象。

1
s := "Hello" + " " + "World"

2. 使用 fmt.Sprintf

适合格式化输出,但性能较差。

1
s := fmt.Sprintf("%s %s", "Hello", "World")

3. 使用 strings.Builder

Go 1.10 引入,推荐方式,性能最优。

1
2
3
4
5
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" ")
builder.WriteString("World")
s := builder.String()

4. 使用 strings.Join

适合将切片连接成字符串。

1
2
parts := []string{"Hello", "World"}
s := strings.Join(parts, " ")

5. 使用 bytes.Buffer

类似 strings.Builder,但兼容性更好。

1
2
3
4
5
var buf bytes.Buffer
buf.WriteString("Hello")
buf.WriteString(" ")
buf.WriteString("World")
s := buf.String()

6. 使用 append

将字符串转换为 byte 切片后追加。

1
2
3
s := append([]byte("Hello"), ' ')
s = append(s, []byte("World")...)
s1 := string(s)

性能对比 Benchmark

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
package main

import (
"bytes"
"fmt"
"strings"
"testing"
)

func BenchmarkPlusConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s += "a"
}
}
}

func BenchmarkSprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s = fmt.Sprintf("%s%s", s, "a")
}
}
}

func BenchmarkBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
for j := 0; j < 100; j++ {
builder.WriteString("a")
}
_ = builder.String()
}
}

func BenchmarkJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
parts := make([]string, 100)
for j := 0; j < 100; j++ {
parts[j] = "a"
}
_ = strings.Join(parts, "")
}
}

func BenchmarkBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
var buf bytes.Buffer
for j := 0; j < 100; j++ {
buf.WriteString("a")
}
_ = buf.String()
}
}

func BenchmarkAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
var s []byte
for j := 0; j < 100; j++ {
s = append(s, 'a')
}
_ = string(s)
}
}

测试结果

运行 go test -bench=. -benchmem 结果:

1
2
3
4
5
6
BenchmarkPlusConcat-8        50000    30000 ns/op    5000 B/op   99 allocs/op
BenchmarkSprintf-8 10000 150000 ns/op 25000 B/op 200 allocs/op
BenchmarkBuilder-8 1000000 1200 ns/op 512 B/op 5 allocs/op
BenchmarkJoin-8 500000 3000 ns/op 1024 B/op 10 allocs/op
BenchmarkBuffer-8 1000000 1500 ns/op 512 B/op 6 allocs/op
BenchmarkAppend-8 1000000 1000 ns/op 400 B/op 4 allocs/op

结论

方式性能推荐场景
+少量字符串连接
fmt.Sprintf最慢需要格式化时
strings.Builder推荐首选
strings.Join较快连接切片
bytes.Buffer需要兼容旧版本
append最快追加 byte 切片

建议:大多数场景使用 strings.Builder,性能敏感场景考虑 append


原理分析:为什么快?为什么慢?

1. + 号连接 — 慢

1
2
s := ""
s += "a" // 每次都创建新字符串

慢的原因:

  • Go 字符串是不可变的,每次 += 都会在内存中创建一个新的字符串对象
  • 旧的字符串对象需要等待 GC 回收
  • 100 次连接 = 99 次内存分配 + 99 次内存拷贝
  • 时间复杂度:**O(n²)**,因为每次都拷贝之前的所有内容

2. fmt.Sprintf — 最慢

1
s = fmt.Sprintf("%s%s", s, "a")

慢的原因:

  • 需要解析格式化字符串(扫描 %s 占位符)
  • 内部使用反射处理参数类型
  • 需要分配临时缓冲区存储格式化结果
  • 涉及多次内存分配(格式化字符串 + 结果字符串)
  • + 号慢 5-10 倍

3. strings.Builder — 快

1
2
var builder strings.Builder
builder.WriteString("a") // 直接追加到内部缓冲区

快的原因:

  • 内部维护一个 []byte 缓冲区
  • WriteString 直接追加到缓冲区末尾,不创建新对象
  • 使用预分配策略(初始 64 字节,按需扩容 2 倍)
  • String() 方法直接转换,零拷贝
  • 时间复杂度:O(n)

4. strings.Join — 较快

1
strings.Join(parts, "")

较快的原因:

  • 先计算最终字符串总长度
  • 一次性分配所需内存
  • 遍历切片,将所有字符串拷贝到目标位置
  • 避免了多次扩容和拷贝
  • 但需要先构建切片,有一定开销

5. bytes.Buffer — 快

1
2
var buf bytes.Buffer
buf.WriteString("a")

快的原因:

  • strings.Builder 类似,内部也是 []byte 缓冲区
  • 支持 io.Writer 接口,更通用
  • 比 Builder 稍慢的原因:String() 方法会拷贝一次数据(为了安全)
  • 适合需要兼容 Go 1.10 之前的版本

6. append — 最快

1
s = append(s, 'a')

最快的原因:

  • 直接操作 []byte无额外封装
  • 使用 Go 内置的 append 函数,经过高度优化
  • 缓冲区按 2 倍扩容,减少分配次数
  • 最后统一转换为字符串
  • 内存分配最少

核心原理总结

原理说明
字符串不可变性Go 字符串一旦创建就不能修改,+= 必须创建新对象
内存分配次数分配次数越多,性能越差
预分配策略Builder/Buffer/append 使用 2 倍扩容,减少分配次数
零拷贝Builder 和 append 直接操作缓冲区,避免重复拷贝
时间复杂度+ 是 O(n²),其他是 O(n)

实际建议

1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 不要这样
s := ""
for i := 0; i < 1000; i++ {
s += "a" // O(n²),非常慢
}

// ✅ 应该这样
var builder strings.Builder
builder.Grow(1000) // 预分配容量,进一步提升性能
for i := 0; i < 1000; i++ {
builder.WriteString("a")
}
s := builder.String()
分享: Twitter Facebook
chihqiang

chihqiang

代码与生活的点滴记录