
本文介绍在 go 语言中避免内存爆炸式增长的 json 序列化技巧:使用 bytes.buffer 替代字符串拼接,实现低开销、高效率的多记录换行输出。
本文介绍在 go 语言中避免内存爆炸式增长的 json 序列化技巧:使用 bytes.buffer 替代字符串拼接,实现低开销、高效率的多记录换行输出。
在 Go 中,频繁使用 += 操作拼接字符串(如 buffer += string(body) + "\n")会引发严重的性能问题。这是因为 Go 的字符串是不可变的,每次拼接都会分配新的底层数组,并复制全部已有内容——当 all_data 规模较大时,时间复杂度趋近于 O(n²),内存占用呈指数级增长,极易触发 OOM。
更优解是采用 bytes.Buffer:它底层基于可动态扩容的字节切片([]byte),所有写入操作均为追加(append),避免重复拷贝。json.Marshal() 返回的本身就是 []byte,可直接调用 buffer.Write() 零拷贝写入;换行符则通过 buffer.WriteString("\n") 高效添加。
以下是推荐的实现方式:
import (
"bytes"
"encoding/json"
"fmt"
)
func marshalRecordsWithNewlines(allData []interface{}) (string, error) {
var buf bytes.Buffer
for _, record := range allData {
body, err := json.Marshal(record)
if err != nil {
return "", fmt.Errorf("failed to marshal record: %w", err)
}
buf.Write(body)
buf.WriteByte('\n') // 更轻量:WriteByte 比 WriteString("\n") 略快
}
return buf.String(), nil
}
// 使用示例
func main() {
data := []interface{}{
map[string]string{"name": "Alice", "age": "30"},
map[string]string{"name": "Bob", "age": "25"},
}
result, err := marshalRecordsWithNewlines(data)
if err != nil {
panic(err)
}
fmt.Print(result)
// 输出:
// {"name":"Alice","age":"30"}
// {"name":"Bob","age":"25"}
}✅ 关键优势:
- 内存分配可控:bytes.Buffer 默认容量为 64 字节,按需倍增(通常仅需 O(log n) 次扩容);
- 零字符串转换:无需 string(body) 强制类型转换,避免额外堆分配;
- 错误可检:显式处理 json.Marshal 错误,提升健壮性;
- 可扩展性强:若后续需写入文件或 HTTP 响应体,可直接使用 buf.Bytes() 或 io.Copy(w, &buf),无需转为字符串。
⚠️ 注意事项:
- 若数据量极大(如百万级记录),仍建议流式写入 io.Writer(如 os.Stdout 或 *os.File),避免将全部结果暂存于内存;
- buf.String() 会在内部执行一次 []byte → string 转换(只发生一次),对最终输出是必要且高效的,不必担忧;
- 切勿在循环中使用 fmt.Sprintf("%s\n", string(body)),其开销与 += 相当,甚至更高。
总结:用 bytes.Buffer 替代字符串拼接,是 Go 中处理批量 JSON 序列化的标准实践。它兼顾简洁性、性能与可维护性,应作为默认选择写入团队编码规范。










