bytes.buffer 复用前必须 reset(),否则 write 追加导致残留数据;bytes.newreader 零拷贝,strings.newreader 几乎无开销,但 string 强转 []byte 会触发拷贝;grow 应在预知长度时调用以避免多次扩容。

为什么 bytes.Buffer 写完不重置就复用会丢数据
因为 bytes.Buffer 的底层是切片,Write 只追加、不自动清空;下次 Write 会接着上次末尾写,不是覆盖。常见于 HTTP handler 里反复用同一个 buf 拼 JSON 或日志——第二次输出里混着第一次的残留字节。
- 复用前必须调用
buf.Reset()(等价于buf.Truncate(0)) - 别用
buf = bytes.Buffer{}重新赋值——这会丢掉原有底层数组引用,失去内存复用价值 - 如果只读不写,
buf.Bytes()返回的是底层数组视图,后续写操作可能覆盖它;需要拷贝时用append([]byte{}, buf.Bytes()...)
bytes.NewReader 和 strings.NewReader 性能差在哪
bytes.NewReader 接收 []byte,直接包装成 io.Reader,零拷贝、无分配;strings.NewReader 接收 string,内部把 string 转成 []byte 视图(unsafe.StringHeader),也几乎没开销。真正慢的是你把 string 强转成 []byte 再喂给 bytes.NewReader——这会触发一次底层数组拷贝,尤其大文本时明显。
- 直接传
string给strings.NewReader,别自己[]byte(s) - 如果已有
[]byte,用bytes.NewReader;两者接口一致,但类型不同,不能混用 - 注意:string 转
[]byte的强制转换(如*(*[]byte)(unsafe.Pointer(&stringHeader)))在 Go 1.20+ 已被禁止,运行时报 panic
用 bytes.Buffer 当临时缓存时,Grow 要不要提前调
要,但只在你知道大概长度时才值得。默认初始容量 64 字节,每次扩容按 2 倍增长,频繁小写入会触发多次 realloc;而提前 buf.Grow(n) 可避免中间扩容,且不会清空已有内容。
- 比如拼接 10 个固定格式的 struct,预估总长 512 字节,就
buf.Grow(512) -
Grow不等于Truncate,它只确保后续写入有足够空间,不影响当前数据 - 过度预估(比如
Grow(1)会浪费内存,尤其并发多实例时;建议结合业务最大值 + 10% 余量
从 bytes.Buffer 读取后还能继续写吗
能,但得小心 Read 改变内部读位置(buf.off)。例如你 buf.Write([]byte("hello")) 后 buf.Read(p) 读走前 3 字节,此时 buf.Len() 是 2,再 Write 会追加到第 4 字节之后——逻辑上“断层”了。
立即学习“go语言免费学习笔记(深入)”;
- 绝大多数场景下,你不需要边读边写;要么全写完再读,要么用
buf.Bytes()拿整块数据处理 - 真要流式读写,考虑
io.MultiReader+bytes.Reader组合,或直接用pipe.Pipe - 调试时打印
buf.Len()和buf.Cap(),比猜更可靠
Buffer 的“缓冲”感其实很弱——它没有自动翻页、没有环形队列,就是个带游标的动态字节数组。用错地方比用不好更常见。










