并发解析 json 数组文件时一读就 oom,是因为 json.unmarshal 或 decoder.decode(&[]t{}) 会将整个数组加载进内存;必须用 dec.token() 跳过 '[' 后循环调用 dec.decode(&item) 逐个解析。

并发解析 JSON 数组文件时为什么一读就 OOM
因为 json.Unmarshal 或 decoder.Decode(&[]T{}) 会把整个数组加载进内存,GB 级文件直接触发 runtime: out of memory。这不是代码写错了,是标准库设计使然——它不提供“跳过容器、逐个消费”的默认模式。
- 必须手动用
dec.Token()跳过'[',再靠dec.More()控制循环边界 - 每次循环只
dec.Decode(&item)一个对象,item可以是map[string]interface{}、自定义 struct 或json.RawMessage - 别用
strings.TrimSpace预处理全文,它又把文件全读进内存了 - 循环退出条件必须是
!dec.More(),不是err == io.EOF;否则会在]后多调一次Decode,报错invalid character '}' after top-level value
XML 文件并发解析要避开哪些共享陷阱
多个 goroutine 不能共用同一个 *xml.Decoder 实例,也不能复用同一份 io.Reader(比如同一个 *os.File)。底层 Decoder 维护内部状态和偏移量,竞态会导致解析错乱或 panic。
- 每个 goroutine 应独立打开文件并创建自己的
xml.NewDecoder - 若需解析同一份 XML 流(如 HTTP body),先用
bytes.NewReader(data)或strings.NewReader(s)复制数据,再分发给各 goroutine - 字段类型不确定时,用
xml:",any"标签接收未声明字段,或用interface{}+ 类型断言动态处理 - 含 CDATA 或混合内容的节点,需在结构体中声明为
string并配合xml:",cdata"标签
worker pool 模式下如何安全分发文件解析任务
直接为每个文件启 goroutine 看似简单,但上万小文件会瞬间耗尽文件描述符(Linux 默认 1024)和内存。worker pool 不是可选优化,而是生产级必需。
- 任务 channel 必须带缓冲:
tasks := make(chan FileTask, 100),避免生产者阻塞 - worker 数量按 I/O 类型设:SSD 上 4–8 个,HDD 上 2–4 个;CPU 密集型任务可适当提高
- 每个
FileTask携带完整路径,goroutine 内部调用os.Open,绝不复用外部句柄 - 循环变量必须显式复制:
path := path,否则闭包捕获的是循环末尾值 - 结果 channel 也需缓冲:
results := make(chan TaskResult, 100),主 goroutine 用for i := 0; i 收集,而非依赖 <code>range遍历已关闭 channel
流式解析时怎么应对字段结构不一致
日志、ETL 数据常混着不同 "type" 的对象,硬套 struct 会 panic。此时不能靠“统一结构体”,而要靠延迟解析和类型弹性。
- 把不确定字段声明为
json.RawMessage,例如Metadata json.RawMessage `json:"metadata"` - 真正需要时再用
json.Unmarshal解析该字段,避免提前失败 - 顶层用
map[string]interface{}解析,再根据type字段分支处理不同 struct - 对关键字段做存在性检查:
if v, ok := data["id"]; ok { ... },不依赖强制类型断言 - 不要在 decode 循环里做耗时操作(如网络请求、数据库写入),应只做解析和轻量转换,重操作放到后续 pipeline 阶段
iostat -x 1。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











