json.decoder 更适合大文件因其边读边解析、内存占用低;而 json.unmarshal 需一次性加载全部数据易oom。

json.Decoder 为什么比 json.Unmarshal 更适合大文件
因为 json.Unmarshal 要把整个 JSON 字符串一次性读进内存再解析,1GB 的文件很可能直接 OOM;而 json.Decoder 是边读边解析,按需解码,内存占用基本只取决于单个对象大小。
典型场景:日志行是独立 JSON(每行一个对象)、API 返回的 streaming JSON(如 Server-Sent Events)、数据库导出的超大 JSON 数组。
- 如果数据是
[{...}, {...}, {...}]这种数组格式,必须先跳过[、处理逗号分隔、识别]结束——json.Decoder自动处理这些,你只需反复调用Decode() - 如果数据是换行分隔(NDJSON),直接对每行新建
json.NewDecoder(strings.NewReader(line))即可,但注意别在循环里频繁创建Decoder实例,复用更高效 - 错误提示常是
invalid character '}' looking for beginning of value——多半是上一次Decode()没读完就重用了Decoder,它内部缓冲区还留着残留字节
如何安全地流式解码 JSON 数组
很多人以为 json.Decoder 不能解数组,其实可以,只是得手动跳过开头的 [ 和结尾的 ],中间用循环逐个解码。
关键点在于:调用 Decode() 前,先用 Token() 吃掉左括号;每次成功解码后,检查下一个 token 是否为 , 或 ],决定是否继续。
立即学习“go语言免费学习笔记(深入)”;
- 用
d.Token()获取第一个 token,确认是json.Delim('{')或json.Delim('['),否则提前退出 - 数组内每个对象解码后,立刻调用
d.Token()看下一个 token:如果是',',继续循环;如果是']',break;其他值(比如'}')说明格式异常 - 不要依赖
io.EOF判断结束——数组末尾的]不会触发 EOF,而是让下一次Token()返回nil,需主动检测
遇到嵌套结构或未知字段时怎么避免 panic
json.Decoder 默认遇到 JSON 字段名在 struct 中没有对应字段,会静默忽略;但如果字段类型不匹配(比如 JSON 给了字符串,Go struct 定义为 int),就会报 json: cannot unmarshal string into Go struct field XXX of type int。
- 对可能缺失或类型不稳的字段,用指针类型(如
*string、*int64),解码失败时该字段保持nil,不会 panic - 用
json.RawMessage接收不确定结构的子字段,后续按需再解码,避免一次性强约束 - 开启
DisallowUnknownFields():调用d.DisallowUnknownFields()可在遇到 struct 没定义的字段时立即报错,方便定位数据格式变更
性能瓶颈常卡在哪儿?几个实测有效的优化点
不是所有“流式”都快——如果解码逻辑本身有阻塞、IO 没缓冲、或频繁分配小对象,吞吐量照样上不去。
- 给底层
io.Reader加bufio.NewReaderSize(r, 64*1024),尤其读文件或网络流时,减少系统调用次数 - 复用 struct 实例:在循环里用
var v MyStruct而不是每次都new(MyStruct),避免 GC 压力 - 避免在
Decode()循环里做耗时操作(如写 DB、HTTP 请求)——先批量收集,再异步处理,否则解码器实际在等 I/O - 如果目标是提取少量字段,考虑用
jsoniter或gjson配合bytes.IndexByte手动切片,比完整解码快 3–5 倍,但 lose 类型安全
最易被忽略的是:decoder 的错误处理粒度。一次 Decode() 失败后,如果不重置 reader 位置或重建 decoder,后续所有解码都会失败——流式处理必须能容忍单条脏数据,而不是整批报废。










