strings.builder 比 + 和 fmt.sprintf 快,因其复用底层字节数组、避免频繁分配与拷贝;+ 每次生成新字符串,fmt.sprintf 需格式解析和反射,开销更大。

strings.Builder 为什么比 + 和 fmt.Sprintf 快
因为 strings.Builder 底层复用字节数组,避免频繁分配内存和拷贝。而 + 每次拼接都生成新字符串(不可变),fmt.Sprintf 还要走格式解析、类型反射,开销更大。
典型场景:循环内拼接日志、生成 HTML 片段、组装 SQL 查询条件——这些地方用 + 容易触发几十次堆分配。
- Builder 初始化后默认容量为 0,首次写入会分配 64 字节;后续扩容按 2 倍增长,类似 slice
- 它不检查 UTF-8 合法性,也不做任何编码转换,纯字节操作,所以快
- 不能直接读取内部数据,必须调用
String()或Bytes()才能获取结果,且调用后继续写入会重新分配
怎么正确初始化和复用 strings.Builder
别每次都在函数里 new 一个——复用能省下初始化和扩容成本。但要注意:它不是 goroutine 安全的,也不能跨函数长期持有未清空的状态。
常见错误:在循环中反复 var b strings.Builder,或忘记 Reset() 就重用。
立即学习“go语言免费学习笔记(深入)”;
- 适合复用的模式:
func formatLog(items []string) string { b.Reset(); for _, s := range items { b.WriteString(s); b.WriteByte(',') }; return b.String() } - 如果 Builder 是局部变量且只用一次,直接
var b strings.Builder即可,不用make或new - 需要预估长度时,用
strings.Builder{Cap: 1024}或构造后调b.Grow(1024),减少扩容次数
WriteString、Write、WriteByte 的选择差异
它们底层都往 buffer 写字节,但语义和性能微有差别。选错不会出错,但可能多一次类型转换或边界检查。
比如 Write([]byte("hello")) 要先转成切片,而 WriteString("hello") 直接走字符串头解析,更快;WriteByte('a') 比 WriteString("a") 少一次字符串头访问。
- 拼接字符串常量或
string变量 → 优先用WriteString() - 拼接单个 ASCII 字符(如分隔符)→ 用
WriteByte(),比WriteString(string(c))少一次分配 - 已有
[]byte数据(如从 io.Read 读来的)→ 用Write(),避免转成 string 再转回来 - 不要用
WriteRune()拼接 Unicode——Builder 不处理 UTF-8 编码逻辑,WriteRune是假的,实际只是把 rune 强转成 byte 写入,会导致乱码
容易被忽略的坑:String() 调用后继续 Write 的行为
String() 返回的是当前 buffer 的拷贝,但 Builder 内部的 addr 字段会被设为 nil,下次 Write 会强制重新分配内存——这跟很多人直觉相反,以为还能接着写。
错误现象:调了 b.String() 后再 b.WriteString("x"),性能突然变差,pprof 显示大量小对象分配。
- 如果需要多次获取中间结果,要么每次都新建 Builder,要么手动保存
b.Bytes()的拷贝(注意它是引用原 buffer) - 想“追加后再取值”,就别提前调
String();真需要中间态,用b.Len()+b.String()截断更安全 - Builder 没有
StringAt(int)这类方法,截断只能靠string(b.Bytes()[:n]),但要注意别越界










