如何结合流式API与DOM访问解析超大JSON指定节点?
大体积JSON指定数组提取实现方案
核心思路:不将全量JSON加载到内存,用流式解析器逐Token遍历文件,匹配到目标数组起始位置后,逐个读取数组元素序列化为字符串存入列表,读完目标数组直接终止解析,无需处理后续内容。内存占用仅和单个数组元素大小相关,可稳定支撑GB级大文件解析,不会触发OOM。
基于Jackson Streaming API实现(推荐,性能更优)
Jackson流式API解析速度快、内存开销低,是大体积JSON处理的首选方案,实现逻辑如下:
- 复用线程安全的
ObjectMapper和JsonFactory实例(避免重复创建带来的性能损耗) - 逐Token遍历根节点字段,非目标字段直接调用
skipChildren()跳过整段子树,不做无意义解析 - 定位到目标数组后,逐个读取数组元素序列化为JSON字符串存入结果集
- 读完目标数组立刻终止解析,不处理后续文件内容
完整实现代码:
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.util.ArrayList; import java.util.List; public class LargeJsonExtractor { // 全局复用实例,不要每次调用方法新建 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final JsonFactory JSON_FACTORY = OBJECT_MAPPER.getFactory(); /** * 从大体积JSON文件中提取指定根层级数组的内容 * @param filePath JSON文件路径 * @param targetArrayName 目标数组字段名 * @return 数组每个元素对应的JSON字符串列表 */ public static List<String> extractArray(String filePath, String targetArrayName) throws Exception { List<String> result = new ArrayList<>(); try (JsonParser parser = JSON_FACTORY.createParser(new File(filePath))) { // 校验根节点格式 if (parser.nextToken() != JsonToken.START_OBJECT) { throw new IllegalArgumentException("非法JSON格式:根节点不是对象"); } // 遍历根节点字段 while (parser.nextToken() != JsonToken.END_OBJECT) { String currentField = parser.getCurrentName(); parser.nextToken(); // 移动到字段值的起始Token if (targetArrayName.equals(currentField) && parser.currentToken() == JsonToken.START_ARRAY) { // 命中目标数组,开始遍历元素 while (parser.nextToken() != JsonToken.END_ARRAY) { JsonNode element = parser.readValueAsTree(); result.add(element.toString()); } // 读取完目标数组直接退出,不解析后续内容 break; } else { // 非目标字段直接跳过整段子树,大幅提升解析速度 parser.skipChildren(); } } } return result; } // 调用示例 public static void main(String[] args) throws Exception { List<String> assetGroups = extractArray("/path/to/your/1.6gb/file.json", "assetGroups"); // 后续业务逻辑直接处理结果即可 } }
关键优化点
- 非目标字段调用
skipChildren()跳过,比逐Token遍历子节点快5~10倍 - 解析完目标数组立刻终止流读取,减少无意义的IO开销
- 全局复用
ObjectMapper和JsonFactory,实例初始化开销降低90%以上
基于Gson Streaming API实现
如果项目已引入Gson、不想额外添加Jackson依赖,可以用相同逻辑实现:
import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import java.io.FileReader; import java.util.ArrayList; import java.util.List; public class GsonLargeJsonExtractor { private static final Gson GSON = new Gson(); public static List<String> extractArray(String filePath, String targetArrayName) throws Exception { List<String> result = new ArrayList<>(); try (JsonReader reader = new JsonReader(new FileReader(filePath))) { reader.beginObject(); while (reader.hasNext()) { String currentField = reader.nextName(); if (targetArrayName.equals(currentField) && reader.peek() == JsonToken.BEGIN_ARRAY) { reader.beginArray(); while (reader.hasNext()) { Object element = GSON.fromJson(reader, Object.class); result.add(GSON.toJson(element)); } reader.endArray(); break; } else { reader.skipValue(); } } reader.endObject(); } return result; } }
相同测试场景下,Gson流式解析的速度约为Jackson的60%,堆内存占用高30%左右,优先选择Jackson实现。
内容的提问来源于stack exchange,提问作者PaulHoran
相关产品推荐
相关产品推荐

