当前位置:首页 > 文章列表 > Golang > Go教程 > Go json.Decoder流式读取大 JSON 数组的内存控制

Go json.Decoder流式读取大 JSON 数组的内存控制

来源:17golang原创 2026-09-15 19:26:38 0浏览 收藏

大型 JSON 数组最容易踩的坑,是把整个文件读进 []byte,再用 json.Unmarshal 生成一份全量切片。数据量一上来,原始字节、解码对象和业务副本会同时占用堆。更稳妥的做法是让 json.Decoder 包住 io.Reader,消费数组边界后逐个 Decode,处理完当前元素就进入下一轮,不把结果全部追加到切片。

要点速览
  • 先用 Token 读取 [,用 More 判断是否还有元素,最后消费 ]
  • 循环变量只保留当前记录;真正的内存峰值仍受单个 JSON 元素和 Decoder 缓冲影响。
  • 大数字、未知字段和坏数据要按业务需要显式处理,不能只靠“流式”二字兜底。

先把全量解码改成逐项处理

假设文件顶层是 [{"id":1,"name":"a"}, ...]。如果业务只需要写入数据库、统计数量或转发记录,就没有必要保留所有对象。结构体接收当前元素,处理函数只拿当前值,下一次 Decode 会覆盖它。

这里的“流式”是处理策略,不等于任何大小的对象都零内存。Go 官方的 encoding/json 文档提供了数组示例;而新版官方说明也指出 v1 解码器会缓冲完整的 JSON value,所以应把目标设为“避免全量数组叠加”,并继续关注单元素大小。

用 Token 和 More 定位数组边界

Go json.Decoder 使用 Token More Decode 读取大型 JSON 数组边界的静态结构说明图
图1:静态结构说明图,查看 Token、More、Decode 与数组边界的关系。
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
)

type Event struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
}

func main() {
	f, err := os.Open("events.json")
	if err != nil {
		panic(err) // 打开失败时没有可继续消费的输入,直接结束示例。
	}
	defer f.Close() // 关闭文件描述符,避免长任务重复打开文件后耗尽资源。

	dec := json.NewDecoder(f)
	first, err := dec.Token()
	if err != nil {
		panic(err) // 先确认顶层确实能读出数组起始 token。
	}
	if delim, ok := first.(json.Delim); !ok || delim != '[' {
		panic("top-level JSON value is not an array") // 输入契约不符时不要误把对象当数组。
	}

	var event Event
	for dec.More() {
		event = Event{} // 清掉上一项,避免可选字段残留到当前记录。
		if err := dec.Decode(&event); err != nil {
			panic(err) // 语法错误或类型不匹配时停止,避免继续处理不可信数据。
		}
		fmt.Printf("process id=%d name=%s\n", event.ID, event.Name) // 这里替换为写库、计数或转发。
	}

	last, err := dec.Token()
	if err != nil || last != json.Delim(']') {
		panic("array is not closed") // 消费右括号,确认数组没有被截断。
	}
	_ = io.EOF // EOF 只适合判断独立 JSON 流的结束,不替代数组边界检查。
}

Token 返回的是数组分隔符,More 只应在当前数组上下文中使用。最后再读一次 Token,可以把“输入被截断”与“正常结束”区分开。示例里的 panic 只是缩短演示;服务代码应返回带上下文的错误。

在循环内处理,而不是积累结果

生产代码通常把 process(event) 换成批量写入器。批量大小可以是固定数量,但批量缓冲也要计入峰值;如果每条记录都追加到 allEvents,就重新失去了 Decoder 的主要收益。需要重试时,保留有限大小的失败队列,并给队列设上限。

选项适用场景内存与正确性提醒
Decode(&struct)字段固定、逐条处理当前元素仍需完整解码到结构体
UseNumber()金额、ID 或超大整数需保留文本精度后续要显式转换并检查错误
DisallowUnknownFields()输入契约严格、希望发现多余字段上游新增字段可能使消费端失败

核对内存边界与上线取舍

Go json.Decoder 处理大型 JSON 数组时 io.Reader 缓冲区和当前记录内存边界的静态说明图
图2:静态内存边界说明图,区分当前元素、Decoder 缓冲区与已处理数据。

排查内存时重点看三层:输入 Reader 的读取缓冲、Decoder 为当前 JSON value 保留的空间、业务处理器暂存的对象。逐项解码能消除“数组长度 × 对象大小”的长期累积,但一个异常巨大的元素仍可能造成峰值。若单元素大小也不可信,应在输入层设置字节上限,或改用能真正按 token 消费的专用方案;不要仅凭 Decoder 名称承诺无限流式。

数字默认解到 float64 可能损失大整数精度。需要保留原始数字时,在第一次读取元素前调用 dec.UseNumber()。需要严格 schema 时可调用 DisallowUnknownFields,但要先确认上游是否允许向后兼容地加字段。

相关问题

为什么用了 Decoder,内存还是会突然升高?

常见原因是单个元素很大,或业务层仍把对象追加到切片、缓存或重试队列。先分别测量元素大小和业务暂存量。

空数组需要特殊处理吗?

不需要。读到 [More 会直接返回 false,随后消费 ] 即可。

遇到数组中间的坏记录能跳过吗?

不能安全地把任意解码错误当成可跳过项。先判断语法和边界是否仍可恢复;不确定时停止并记录输入位置。

什么时候应该保留全量切片?

只有后续算法确实需要全局排序、去重或多次关联时才保留,并为数据量设置上限;单纯逐条导入不应默认全量加载。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PHP stream识别文件与 HTTP wrapper 的差异的实现方法PHP stream识别文件与 HTTP wrapper 的差异的实现方法
上一篇
PHP stream识别文件与 HTTP wrapper 的差异的实现方法
Java virtual thread定位 synchronized 导致的固定载体线程的实现方法
下一篇
Java virtual thread定位 synchronized 导致的固定载体线程的实现方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    43次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    138次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    74次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    39次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    26次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码