字符串拼接效率怎么提升?strings.Builder 为什么快?

一句话结论:少量拼接用 + 没问题;高频或循环拼接用 strings.Builder:它内部维护 []byte,WriteString 就是 append,最后 String() 零拷贝转成 string,避免不可变字符串反复创建与复制;能预估大小时先 Grow 预分配,把扩容次数降到最低。

各方式对比

方式原理特点适用
+ 运算符每次生成新字符串并复制简单直观2~3 个短串
fmt.Sprintf反射 + 格式化最慢,有非必要开销需要格式化时
strings.Join一次性分配并拼接快,要求统一分隔符切片按分隔符合并
strings.Builder[]byte append,String 零拷贝快,支持 Grow循环/高频拼接

注意:循环里写 s += x 会让每次迭代都分配一块新内存并复制旧内容,总代价退化为 O(n²)。

为什么 strings.Builder 快

  • 底层就是 []byte:写字符串等于 append,可原地扩容,不复制“旧串”。
  • Grow(n) 预分配:按最终大小一次扩容,避免多次翻倍搬移。
  • String() 用 unsafe 把 []byte 直接转成 string,不做拷贝。
  • 代价是返回值与内部缓冲共享底层数组:String() 之后再继续写入可能影响已返回的串,使用上要留意。
// 高吞吐场景的推荐写法
var sb strings.Builder
sb.Grow(estimateLen) // 预估总长度,尽量一次扩容
for _, s := range parts {
	sb.WriteString(s)
	sb.WriteByte(',')
}
result := sb.String()

sb.Reset() // 循环复用

常见追问 / 记忆点

  • 追问:为什么 fmt.Sprintf 慢?——内部走反射解析格式串,还有中间分配;非格式化场景应避免。
  • 追问:Builder 适合并发使用吗?——不适合,同实例需自己加锁或每 goroutine 一个。
  • 记忆点:高频拼接三连:Builder + Grow 预分配 + Reset 复用;别在循环里用 +。
笔记加载中…