多次 strconv 转字符串性能差是因为每次调用都分配新 []byte 并转 string,导致频繁内存分配和 gc 压力;bytes.buffer 通过缓冲写入、避免中间 string 构造来减少分配,配合 strconv.appendint 可实现零分配追加。

为什么多次 strconv 转字符串会拖慢性能?
每次调用 strconv.Itoa、strconv.FormatInt 等函数,都会分配新的 []byte,再转成 string —— 这个过程绕不开内存分配和 GC 压力。尤其在高频拼接场景(比如日志格式化、协议序列化),反复转换小整数,临时对象堆积明显。
真正开销不在转换逻辑本身,而在堆上频繁申请短生命周期的字符串底层字节数组。
bytes.Buffer 怎么帮我们“攒着一起写”?
bytes.Buffer 本质是带扩容策略的可变字节切片,它把多次写入缓冲到同一块内存里,最后一次性转 string 或直接写入 io.Writer。关键在于:它提供 WriteString 和 Write 方法,能跳过中间 string 构造,直接追加字节。
- 对整数,优先用
fmt.Fprint(b, n)或b.WriteString(strconv.FormatInt(n, 10))—— 后者仍有一次转换,但只生成一次临时字符串 - 更优的是用
strconv.AppendInt直接操作Buffer.Bytes()底层切片,零分配写入 -
Buffer初始容量设合理(如bytes.NewBuffer(make([]byte, 0, 256)))能减少扩容次数
strconv.AppendInt 配合 bytes.Buffer 的正确姿势
strconv.AppendInt 不返回 string,而是把数字转成字节追加到输入切片末尾并返回新切片。所以不能直接传 Buffer.Bytes() 给它——因为 Buffer.Bytes() 返回的是只读视图,且长度/容量可能不匹配。
必须用 Buffer.Grow() 预留空间,再通过 Buffer.Bytes() 获取底层数组,手动管理长度:
// 正确写法:先扩容,再取底层数组追加 b := bytes.NewBuffer(make([]byte, 0, 128)) b.Grow(20) // 预估足够存一个 int64 字符串(最多 20 字节) buf := b.Bytes() n := strconv.AppendInt(buf[:b.Len()], int64(12345), 10) b.Truncate(0) b.Write(n)
更实用的模式是封装辅助函数,避免每次手动 Truncate 和 Write;或者干脆放弃 Buffer,直接用 make([]byte, 0, 128) + strconv.Append* 手动拼接,最后 string() 一次转换。
什么情况下不值得用 bytes.Buffer?
单次拼接、变量少于 3–4 个、结果字符串总长低于 64 字节时,直接 fmt.Sprintf 或 strconv + + 拼接反而更快 —— Buffer 的方法调用和状态维护有固定开销。
注意:Buffer.String() 会复制底层数组,如果后续还要写入,别反复调用它;需要复用时,用 Buffer.Reset() 而不是新建实例。
真正省下的不是 CPU 时间,是 GC 压力。压测时看 allocs/op 和 heap_allocs 比看 ns/op 更说明问题。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











