如何高效处理超大JSON数据?通过懒加载、流式解析提升大 JSON 处理效率

JsonTool
订阅
充电
|

适用对象:后端工程师、数据平台开发者
典型场景:大体积 API 响应(MB/GB 级)、日志/ETL 导入、大模型数据集处理

1. 大 JSON 的痛点

在工程里遇到过这种场景:

  • 一次性加载:前端传过来一个 200MB 的 JSON 文件,如果用 JSON.parse() 或 Jackson 的 readTree(),直接就会把整个文件展开到内存。8 核 16G 的服务器瞬间卡主,GC 一直在跑。
  • 数组超大:像日志、交易流水、埋点数据,经常是一个 JSON 数组里几百万条对象。内存里 hold 不住,但业务逻辑只需要逐条处理并写库。
  • I/O 与 CPU 不匹配:磁盘和网络是流式的,但我们偏要一次性把 JSON 吃干抹净,再开始处理,白白浪费了 pipeline 的机会。

所以,核心问题就是:如何避免一口吃撑”


2. 懒加载:用到再解析

懒加载的思路很简单:不要提前把整个树结构展开,而是保留游标,等到访问时再解析

在 Java 里,可以用 Jackson 的 JsonNode 懒绑定特性,或者自定义 Iterator 包装流。

示例:Jackson 的懒解析

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectMapper mapper = new ObjectMapper();
try (InputStream in = new FileInputStream("big.json")) {
JsonParser parser = mapper.getFactory().createParser(in);

// 先定位到数组
if (parser.nextToken() == JsonToken.START_ARRAY) {
while (parser.nextToken() == JsonToken.START_OBJECT) {
// 按需绑定:只解析当前对象,不展开整个数组
MyRecord record = mapper.readValue(parser, MyRecord.class);
process(record);
}
}
}

这样做的好处:

  • 单条对象级别的懒解析:不用一次性展开整个数组。
  • 内存占用可控:常驻内存只存一条记录。

坏处也明显:如果你需要频繁随机访问(比如第 999999 条对象),就不如一次性载入了。

3. 流式解析:边读边处理

如果懒加载是“访问才算账”,流式解析就是“流水账”,不记本子。

主流 JSON 库大多支持流式 API,比如:

  • Jackson Streaming APIJsonParser/JsonGenerator
  • Gson StreamingJsonReader
  • Python ijson
  • Node.js JSONStream

示例:Python 处理 GB 级 JSON

1
2
3
4
5
6
import ijson

with open("big.json", "rb") as f:
for item in ijson.items(f, "rows.item"):
# 假设 big.json = { "rows": [ {...}, {...} ] }
process(item)

特点:

  • 内存里永远只有一条对象。
  • 可以直接串到下游(数据库批量写入、消息队列)。

实际测试下来,1GB 的 JSON 文件在 8G 内存机器上几乎没压力,CPU 基本就是序列化/反序列化本身的成本。

4. 工程实践中的取舍

(1)什么时候用懒加载?

  • 中等体量 JSON(几十 MB 级):一次性解析略显浪费,但又想保留对象树结构,适合懒加载。
  • 有条件访问场景:比如只会访问其中某些字段或部分节点。

(2)什么时候用流式解析?

  • 日志/流水/埋点类数据:天然就是“顺序处理”,适合边读边写。
  • 超大 JSON(GB 级):唯一可行方案,内存 hold 不住。

(3)性能数据对比

以 500MB 的 JSON 数组(500 万条对象)为例,在一台 16G 内存的机器上:

  • 一次性 parse:耗时约 35s,内存峰值 8GB+,GC 频繁。
  • 懒加载:耗时约 28s,内存峰值 < 1GB。
  • 流式解析:耗时约 25s,内存常驻 ~200MB。

注:数据来自实际测试,因语言/库实现不同会有差异,但趋势一致。

5. 额外技巧

  • 切块存储:上游可以把超大数组切成 NDJSON(每行一个对象),这样天然流式友好,Fluent Bit / Spark 都能吃。
  • 配合异步/批处理:解析一条→放进队列→消费者写库,比解析完一大坨再批量处理更均衡。
  • 错误容忍:流式解析时更容易做到“跳过坏行继续跑”,避免全量失败。

6. 小结

到这里总结下, 大 JSON 不要一口吞,用懒加载减少不必要的展开,用流式解析把顺序处理做成流水线。

这两个思路不是互斥的:读大文件时用流式解析,进入业务层时再懒加载字段,可以把效率和易用性都兼顾。