优先用 strings.Builder 构建纯字符串,因其零拷贝写入、仅 String() 时一次转换;bytes.Buffer 适合需 io.Writer 或读操作的场景,且其 String() 每次都重新分配。

什么时候该用 strings.Builder 而不是 bytes.Buffer
当目标是构建纯字符串(string 类型)且**不涉及二进制数据、不需读取中间状态、不需要实现 io.Reader 接口**时,优先选 strings.Builder。它专为字符串拼接优化,零拷贝写入底层 []byte,最后调用 String() 时才做一次转换,没有额外分配。
常见误用场景:用 bytes.Buffer 拼接日志、SQL 模板、HTML 片段后,再调用 b.String() —— 这类场景完全可换 strings.Builder,省去 bytes.Buffer 内部的 io.Writer 接口开销和冗余方法。
-
strings.Builder不支持Read()、Next()、Reset()等读操作,也不实现io.Reader -
bytes.Buffer的String()每次都重新分配并拷贝底层字节,即使内容没变 - 若后续要传给
io.WriteString()或写入文件,bytes.Buffer可直接作为io.Writer;strings.Builder不行
strings.Builder 的初始化和扩容行为
strings.Builder 底层持有 []byte,但不暴露长度控制接口。它的 Grow(n) 方法只预估容量,不保证立即分配;而 bytes.Buffer 的 Grow(n) 会确保至少有 n 字节可用空间。
如果你知道最终字符串大致长度(比如拼接 100 个固定长的 ID),显式调用 Grow() 能避免多次底层数组扩容:
立即学习“go语言免费学习笔记(深入)”;
var b strings.Builder
b.Grow(1024) // 预分配 1KB,减少 reallocation
for i := 0; i < 100; i++ {
b.WriteString("id:")
b.WriteString(strconv.Itoa(i))
b.WriteByte(',')
}
- 不调用
Grow()时,strings.Builder初始容量为 0,第一次写入触发默认增长(类似 slice 扩容:0→64→128→256…) -
bytes.Buffer默认初始容量也是 0,但它的WriteString()在扩容时还会多拷贝一次(因要维护off偏移量) -
strings.Builder的Len()返回当前字符串长度,Cap()不暴露 —— 你无法直接获取底层切片容量
拼接性能对比:实测差异在哪
在纯追加(append-only)场景下,strings.Builder 比 bytes.Buffer 快约 10%–20%,主要省在三处:
- 无
io.Writer接口间接调用开销(bytes.Buffer的WriteString()是接口方法) -
String()不重复拷贝:它内部用unsafe.String()直接构造字符串头,不复制字节 - 少维护一个
off字段(bytes.Buffer需支持从任意位置读写)
但注意:这个差距只有在高频小字符串拼接(如模板渲染、日志组装)中才明显。如果单次拼接几十 KB 以上,或中间夹杂大量条件分支,编译器优化和内存分配器的影响会盖过这点差异。
别忽略的边界情况:strings.Builder 不能重用
strings.Builder 的 Reset() 方法清空内容,但**不会释放底层内存**;而 bytes.Buffer.Reset() 同样不清内存,但你可以手动置空:b = bytes.Buffer{} 触发新分配。
更关键的是:strings.Builder 的零值不可直接复用 —— 它的底层 buf 字段未导出,且文档明确说「zero value is ready to use」,但一旦调用过 String(),再写入可能 panic(Go 1.22+ 已修复,但旧版本仍需注意)。稳妥做法是:
- 局部变量:直接声明新
strings.Builder{},别试图复用 - 池化场景:用
sync.Pool缓存,但取出后必须调用Reset(),且不能在String()后继续写 - 若需反复拼接 + 读取 + 清空,
bytes.Buffer更安全——它所有状态可控,且Reset()行为稳定
真正影响选择的往往不是性能数字,而是你是否需要那个被砍掉的读能力、接口兼容性,或者团队是否已习惯某一种模式。别为了 15% 的提升,把 bytes.Buffer 在 HTTP handler 里拼 JSON 的逻辑硬换成 strings.Builder,除非你确认后续永远只输出字符串。










