strings.builder 比 += 快得多,因 string 不可变,+= 每次都需分配新内存并拷贝旧内容(o(n²)),而 builder 底层用可增长 []byte,扩容均摊 o(1),且 string() 零拷贝。

为什么 strings.Builder 比 += 快得多
因为 Go 的 string 是不可变的,每次用 += 都得分配一块新内存、把旧内容全拷过去,再追加新内容。拼 100 次,就要拷贝约 5000 次字节(O(n²));而 strings.Builder 底层用的是可增长的 []byte,写入是追加,扩容策略类似 slice(通常是翻倍),均摊下来每次写入接近 O(1)。
- 1000 次短字符串拼接,
+=可能触发上千次内存分配;strings.Builder预分配后通常只分配 1–2 次 -
String()方法在 Go 1.10+ 是零拷贝——它直接把底层[]byte转成只读string,不复制数据 - 编译器不会帮你把循环里的
+=自动转成Builder,哪怕你拼的是常量
怎么用 strings.Builder 才真正省资源
光换类型不够,得配合预分配和正确调用习惯。否则可能只是“换汤不换药”,甚至更慢。
- 能预估总长就调
b.Grow(n):比如拼 50 个平均 30 字符的 ID,b.Grow(1500)可避免中间扩容 - 优先用
WriteString,别用Write([]byte(s)):前者少一次切片转换,也更安全 -
String()只调一次:它返回的是副本视图,反复调不会报错,但每次都会重新构造字符串头(虽不拷数据,仍不必要) - 局部声明最省心:
var b strings.Builder放函数里,不用Reset()也能复用底层数组(零值可用)
哪些场景下其实没必要用 strings.Builder
不是所有拼接都值得优化。小量、固定、编译期可知的拼接,+= 更简洁,且 Go 编译器(1.20+)会自动合并为单次分配。
- 2–3 个字符串拼一起,比如
"GET " + path + " HTTP/1.1",用+更直观 - 拼接内容全是常量或少量变量,且不在循环里,
fmt.Sprintf或+都没问题 - 要拼的是已存在的切片(如
[]string{"a", "b", "c"}),直接用strings.Join,比 Builder 还快
常见误用:复用 strings.Builder 却忘了 Reset()
很多人把 Builder 当全局变量或池里复用,结果第二次拼出来是第一次 + 第二次的内容——因为它不会自动清空,String() 也不重置状态。
立即学习“go语言免费学习笔记(深入)”;
- 循环中复用必须在每次迭代开头调
b.Reset():它只重置长度(len),不释放底层内存,零分配 - 跨 goroutine 共享必须加锁:它不是线程安全的,也没有内部 mutex
- 别对
Builder取地址传参后修改:它的非导出字段不保证行为,容易导致原实例没变化、逻辑错乱
String(),又马上 WriteString,又立刻 String(),那跟 += 差不多,还多了 API 开销。











