直接使用json.unmarshal或xml.unmarshal解析大文件会导致内存爆满,因其需将整个文件读入[]byte;应改用基于io.reader的json.decoder和xml.decoder实现流式解析,内存占用仅取决于单个对象深度与字段长度。

为什么不能直接用 json.Unmarshal 或 xml.Unmarshal 解析大文件
内存会爆。这两个函数要求把整个输入读进 []byte 再解析,100MB 的 JSON 文件就占 100MB 内存,加上结构体反射开销,实际可能翻倍。流式解析不是“选配”,是硬性需求。
真正能扛住大流量、大体积的方案,只有 json.Decoder 和 xml.Decoder —— 它们基于 io.Reader,边读边解,内存占用基本只取决于单个对象深度和字段长度。
- 别碰
json.Unmarshal+os.ReadFile组合,哪怕加了//nolint也救不了 -
xml.Unmarshal同理,它内部会先把整个 XML 加载成bytes.Buffer - 如果上游给的是 HTTP body,直接传
resp.Body给json.NewDecoder,别先ioutil.ReadAll
怎么用 json.Decoder 做流式解码并跳过无关字段
核心是控制解码粒度:不一次性解整个数组,而是用 Decode 循环读每个元素。配合 struct{} 或 map[string]interface{} 可跳过不需要的嵌套层。
常见错误是写成 dec.Decode(&v) 一次解完——这等于又回到全量加载的老路;正确做法是明确知道数据结构(比如外层是数组,每项是对象),然后手动推进。
立即学习“go语言免费学习笔记(深入)”;
- 用
dec.Token()判断当前 token 类型(json.Delim、string、float64等),跳过不想处理的字段名或值 - 对数组,先读
[,再循环for dec.More() { ... },每次dec.Decode(&item) - 如果字段名不确定,用
map[string]json.RawMessage接收,只对关心的 key 做二次解码,避免反序列化全部字段
var dec = json.NewDecoder(r)
dec.Token() // 跳过 '['
for dec.More() {
var item struct {
ID int `json:"id"`
Data string `json:"data"`
}
if err := dec.Decode(&item); err != nil {
// 处理单条失败,不影响后续
continue
}
process(item)
}
xml.Decoder 解析时如何避免 invalid character 和命名空间爆炸
XML 流式解析比 JSON 更容易栽在细节上:xml.Decoder 默认不忽略空白,遇到换行缩进就报 invalid character;命名空间前缀(如 ns0:)若没注册,字段就绑定不上。
根本原因不是 XML 写得错,而是解码器默认行为太严格。必须主动调用 decoder.CharsetReader 和 decoder.Strict(false),否则连标准 HTTP 响应里带 BOM 或 UTF-8-BOM 的 XML 都过不去。
- 务必设
dec.Strict(false),否则注释、CDATA、未闭合标签直接 panic - 用
dec.Entity = map[string]string{"nbsp": " "}处理 HTML 实体(常见于混合内容) - 命名空间字段要用
xml.Name字段接收,再按Space和Local手动匹配,别依赖自动映射 - 如果 XML 有 DTD 声明,提前用
bytes.ReplaceAll干掉/code> 行,<code>xml.Decoder不支持网络/DTD 解析
指针在流式结构体字段中的真实作用:不是为了节省内存,而是控制零值覆盖
很多人以为加 *string 是为了省几个字节——其实完全不是。关键在于:JSON/XML 中缺失字段 vs 显式 null vs 空字符串,在 Go 结构体里语义完全不同。用指针才能区分“没传”和“传了空值”。
比如 API 返回 {"name":"alice"},如果字段是 name string,解出来就是 "alice";但如果返回 {},它会变成 "",你无法判断客户端是否故意清空了 name。而 name *string 在缺失时是 nil,显式 null 是非 nil 但指向空字符串。
- 所有需要区分“未提供”和“提供为空”的字段,都该用指针类型(
*string、*int64、*bool) - 切片字段(
[]T)不用加星号,JSON 空数组[]和缺失字段都解成nil,语义一致 - 嵌套结构体字段如果也要区分存在性,定义成
*InnerStruct,但注意解码时要确保其字段也支持 nil 安全访问
流式场景下这点尤其关键:你没法重读上游数据来二次确认字段是否存在,指针是唯一能保留原始语义的机制。










