应使用 json.newdecoder 配合 io.reader 流式解析大 json 文件,避免 json.unmarshal 全量加载导致 oom;数组需手动跳过容器边界并循环解码;动态字段用 json.rawmessage 而非 interface{};序列化大量数据时用 bytes.buffer 替代字符串拼接。

别用 json.Unmarshal 加载整个文件
直接 json.Unmarshal 读取大 JSON 文件(尤其是 GB 级)必然 OOM。它要求先把全部内容读进 []byte,再反射解析——内存占用≈文件大小 × 2~3 倍。哪怕只是想取前 10 条,也会全量加载。
正确路径是绑定 io.Reader:用 json.NewDecoder(r),其中 r 可以是 *os.File、http.Response.Body 或任何流式输入源。解码器边读边解析,内存只维持当前 token 和目标结构体实例。
- 绝对不要写
data, _ := os.ReadFile("big.json"); json.Unmarshal(data, &v) - HTTP 场景下,直接传
resp.Body给json.NewDecoder,别先ioutil.ReadAll或io.ReadAll - 如果上游带 gzip,记得用
gzip.NewReader(resp.Body)包一层,再传给json.NewDecoder
数组顶层必须手动跳过容器边界
遇到形如 [{"id":1},{"id":2},...] 的巨型数组,decoder.Decode(&[]T{}) 仍会全量分配底层数组并加载所有元素——这不是流式,是假流式。
真正能控内存的解法是三步手动推进:
- 调一次
dec.Token(),确认并跳过'['(返回值应为json.Delim('[')) - 进入
for dec.More() { ... }循环,dec.More()内部自动识别逗号分隔和结尾']' - 每次循环内调
dec.Decode(&item),item可是struct、map[string]interface{}或json.RawMessage
漏掉 dec.More() 判断,或循环条件写成 for { if err == io.EOF { break } },就会在读完最后一个元素后多 decode 一次,触发 invalid character '}' after top-level value 错误。
字段不确定时优先用 json.RawMessage
日志、ETL 数据常混着不同 "type" 的对象,硬写 struct 容易 panic 或静默丢字段。比如 "metadata" 有时是空值、有时是字符串、有时是嵌套对象。
把这类字段声明为 json.RawMessage,跳过即时解析:
type LogEntry struct {
ID int `json:"id"`
Type string `json:"type"`
Metadata json.RawMessage `json:"metadata"`
}
后续按需处理:json.Unmarshal(entry.Metadata, &target)。这样既避免全量反序列化失败,又防止因类型错位导致整个 item 被丢弃。
- 别用
interface{}接收动态字段——反射开销大,且无法跳过无效字段 -
json.RawMessage是字节切片引用,不拷贝原始数据,内存友好 - 注意:它保留原始 JSON 字节,包括空格和换行;若需干净字符串,得自己 trim 或重新 marshal
序列化大量结构体别拼字符串
生成 NDJSON(每行一个 JSON)时,用 buffer += string(body) + "\n" 是灾难。Go 字符串不可变,每次 += 都分配新底层数组并复制全部历史内容,内存增长呈 O(n²)。
换成 bytes.Buffer:
var buf bytes.Buffer
for _, record := range records {
body, _ := json.Marshal(record)
buf.Write(body)
buf.WriteByte('\n')
}
result := buf.Bytes() // 直接拿 []byte,或 buf.String() 最后转一次
bytes.Buffer 底层是可扩容的 []byte,Write 和 WriteByte 复用同一块内存,均摊写入开销接近 O(1)。上万条记录下,内存峰值能从几百 MB 降到几 MB。
更进一步:如果目标是写文件或 HTTP 响应,直接 io.Copy(writer, &buf) 或 json.NewEncoder(writer).Encode(item),避免中间 []byte 拷贝。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











