Go 里什么时候该用 strings.Builder,什么时候该用 bytes.Buffer
刚开始写 Go 的时候,遇到文本拼接最容易想到的还是 +。代码少时当然没问题,但一旦进入循环、日志拼接、模板前处理或者接口响应组装,这种写法很快就会让代码既不清晰,也不够稳妥。真正开始做一些小工具之后,我发现自己最常用的其实不是花哨技巧,而是把 strings.Builder 和 bytes.Buffer 的边界分清楚。
先别急着把所有拼接都优化成“高性能写法”
很多人一上来就把所有字符串都改成 Builder,结果代码变长了,收益却不明显。我的经验是先看场景,再决定工具。如果只是两三个常量和变量拼在一起,+ 最直观;如果是在循环里不断累加字符串,或者要逐段构造一大块文本,那就该考虑 Builder 了。
真正麻烦的不是“慢一点”,而是你在循环里一边读业务逻辑,一边还得猜最终字符串长什么样。Builder 的好处不只是减少中间对象,它让“我正在顺序写入一段文本”这件事表达得更明确。
strings.Builder 更像是“只关心文本结果”
当最终目标就是一个字符串,而且中间不会把内容当成字节流再处理时,strings.Builder 的意图最清楚。比如拼一段 SQL 片段、日志消息、批量生成 markdown 片段,都是很自然的使用场景。
我比较喜欢把它放进小函数里,而不是一路在主流程里到处写。这样读代码的人一看函数名,就知道这段逻辑的职责是“组装文本”,而不是把业务判断和细节输出搅在一起。
func buildLogLine(level, module, message string) string {
var b strings.Builder
b.WriteString("[")
b.WriteString(level)
b.WriteString("] ")
b.WriteString(module)
b.WriteString(": ")
b.WriteString(message)
return b.String()
}
这种写法的重点不是炫技,而是让文本结构一眼可见。以后要调整格式,也比满地 + 更不容易漏。
bytes.Buffer 适合还在“字节阶段”的工作流
如果中间过程还会处理字节、写入文件、拼接二进制内容,或者你后面要把结果交给一个接受 []byte 的接口,那 bytes.Buffer 会更顺手。它不是 Builder 的替代品,而是另一种工作流。
比如你要拼接 HTTP 响应片段、缓存模板渲染结果、临时接收命令输出,或者后面还要继续往 io.Writer 体系里传,Buffer 往往更自然。很多时候你不是“最终要一段字符串”,而是“先把内容积起来,后面再决定怎么消费”,这就是 Buffer 更合适的地方。
不要把“工具选择”变成“写法洁癖”
我后来踩过一个小坑:本来只是一段很简单的文本,非要提前引入 Buffer 或 Builder,最后主流程里充满了写入细节,真正的业务判断反而不清楚。这个时候即使性能看起来更“专业”,代码的可维护性却在下降。
更稳的做法是给自己一个简单判断:
- 最终结果就是字符串,优先考虑
strings.Builder - 中间还要按字节流处理,优先考虑
bytes.Buffer - 只拼几段很短的内容,直接
+反而更清楚
先把工具边界想明白,比盲目优化更重要
Go 标准库已经把这些常用场景考虑得很充分了。真正值得练习的,不是死记哪个“更快”,而是理解自己当前在处理“文本”还是“字节”,代码到底是为了让机器跑得更顺,还是也让下次看代码的人更容易接手。
只要这层边界清楚,很多原本纠结的小问题都会变得很好选。写工具函数也好,做接口拼装也好,代码会自然地稳定下来。
