会,直接用 json.Unmarshal 解析大文件极易触发 OOM Killer,因其需全量加载 JSON 到内存且占用为原文件 2~3 倍;应改用 json.Decoder 配合 io.Reader 流式解析。

直接用 json.Unmarshal 解析大文件会 OOM 吗?
会,而且非常容易。只要文件超过几十 MB,json.Unmarshal 就大概率触发系统 OOM Killer。它必须先把整个 JSON 读进 []byte,再反射解析——内存占用通常是原始文件体积的 2~3 倍。哪怕你只想要第一个对象,它也得全加载。
json.Decoder 怎么绑定流式输入源?
关键不是“怎么调用”,而是“传什么进去”。json.NewDecoder 接收的是 io.Reader,不是字节切片:
- 文件:直接
os.Open("huge.json")→json.NewDecoder(f),别用os.ReadFile - HTTP 响应:
json.NewDecoder(resp.Body),别先io.ReadAll(resp.Body) - 带缓冲:对慢设备或小包场景,套一层
bufio.NewReader提升吞吐,但不是必须 - 压缩流:如果服务端返回 gzip,先
gzip.NewReader(resp.Body),再传给json.NewDecoder
错误做法:json.NewDecoder(bytes.NewReader(data))(data 是 os.ReadFile 的结果)——这没省内存,只是 API 换了壳。
顶层是 JSON 数组时,为什么 dec.Decode(&[]T{}) 还是爆内存?
因为那不是流式,是假流式。Decode 会试图把整个数组反序列化成 Go slice,底层数组仍要一次性分配所有元素空间。
立即学习“go语言免费学习笔记(深入)”;
真正可控的解法是手动推进三步:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
- 调
dec.Token()确认并跳过开头的'['(检查返回是否为json.Delim('[')) - 用
for dec.More() { }循环,dec.More()内部自动识别逗号分隔和结尾']' - 每次循环内调
dec.Decode(&item),item可以是struct、map[string]interface{}或json.RawMessage
高频崩溃点:dec.More() 返回 false 后还继续 Decode,报错 invalid character '}' after top-level value。建议循环开头加 if !dec.More() { break },比依赖 io.EOF 更可靠。
字段结构不确定时,该用 interface{} 还是 json.RawMessage?
用 json.RawMessage。它只是原 JSON 字节的引用([]byte subslice),不触发解析,内存开销极小;而 interface{} 会递归构建 map/slice/number 结构,一碰到嵌套深或字段爆炸(比如含 base64 图片),立刻卡住或爆内存。
典型用法:
type Log struct {
ID int `json:"id"`
Metadata json.RawMessage `json:"metadata"`
}
后续按需解析:json.Unmarshal(log.Metadata, &target)。注意:json.RawMessage 不能直接用在 map[string]interface{} 的 value 里(会 panic),只能用于 struct 字段或 slice 元素。
最常被忽略的细节:流式解析不是“开了 Decoder 就万事大吉”,而是每一步推进都得明确上下文边界——数组要跳 [、对象要跳 {、字符串值要区分引号内外。漏掉一个 dec.More() 判断,或误把 RawMessage 当 interface{} 用,都会让内存曲线突然翘起。

















