Java实现解析JSON大文件JsonReader工具详解
作者:李奈 发布时间:2023-08-21 21:13:14
一,使用背景
之前遇到一个需求,是需要将一个json文件解析存储到数据库中。一开始测试的时候,json文件的大小都在几兆以内,所以直接将json文件转化为字符串,再转化成JSONObject对象进行处理时不会出现问题,如下所示:
File file = new File("")
try(FileInputStream fileInputStream = new FileInputStream(file)) {
int size = fileInputStream.available();
byte[] buffer = new byte[size];
fileInputStream.read(buffer);
String jsonString = new String(buffer, StandardCharsets.UTF_8);
jsonString.replaceAll("\n", "");
jsonString.replaceAll("\r", "");
JSONObject json = JSON.parseObject(jsonString);
}
但是,当出现几十兆文件的时候,这时候就会报出内存溢出的错误
java.lang.OutOfMemoryError: Java heap space
虽然稍微大一点的文件,可以通过调整JVM参数来解决,如下所示
-Xms512m -Xmx2048m
但是这毕竟不是最合理的方法,因为当文件大到一定程度后,字节数组和字符串类型都存在接收不了的情况。因此,只能选择另外的方式,此时,Google的JsonReader是一个不错的解决方案。
二,JsonReader的使用
maven依赖如下:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
JsonReader读取 JSON (RFC 7159) 编码值作为令牌流。 此流包括文字 值(字符串、数字、布尔值和空值)以及开始和 对象和数组的结束分隔符。 令牌被遍历 深度优先顺序,与它们在 JSON 文档中出现的顺序相同。 在 JSON 对象中,名称/值对由单个标记表示。
解析json
创建递归下降解析器 JSON ,首先创建 创建一个入口点方法 JsonReader
.
每个对象类型和每个数组类型都需要一个方法。
在 数组处理 方法中,首先调用 beginArray()消耗数组的左括号。 然后创建一个累积值的while循环,在何时终止 hasNext()为false。 最后,通过调用读取数组的右括号 endArray()
在 对象处理 方法中,首先调用 beginObject()消耗对象的左大括号。 然后创建一个while循环根据局部变量的名称为其赋值。 这个循环应该在什么时候终止 hasNext()为false。 最后,通过调用读取对象的右括号 endObject().
当遇到嵌套对象或数组时,委托给对应的处理方法。
当遇到未知名称时,严格的解析器应该失败并返回。 但宽松的解析器应该调用 skipValue()递归地 跳过值的嵌套标记,否则可能会发生冲突。
如果一个值可能为空,应该首先检查使用 peek(). 空字面量可以使用 nextNull()或者 skipValue().
例如,我之前要解析的json文件格式如下:
{
"INFO": {
"NAME": "",
"Result": "",
"Config": "",
...
},
"ATTR": {
"key01": "val01",
"key02": "val02",
...
},
"Parms": [
{
"k": "",
"v": "",
"p": "",
"m": "",
"l": ""
},
{
"k": "",
"v": "",
"p": "",
"m": "",
"l": ""
},
...
],
"List": ["xxx", "xxxx", ...]
}
那按照JsonReader解析的思路,我应该先消费整体对象的{,再逐个对INFO,ATTR,Parms,List进行处理,总而言之,就是
String fileName = "";
FileReader in = new FileReader(fileName);
JsonReader reader = new JsonReader(in);
reader.beginObject();
String rootName = null;
while (reader.hasNext()) {
rootName = reader.nextName();
if("INFO".equals(rootName)) {
reader.beginObject();
while (reader.hasNext()) {
System.out.println(reader.nextName() + ":" + reader.nextString())
}
reader.endObject();
}else if("ATTR".equals(rootName)) {
reader.beginObject();
while (reader.hasNext()) {
System.out.println(reader.nextName() + ":" + reader.nextString())
}
reader.endObject();
}else if("Parms".equals(rootName)) {
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject();
String k = null;
while (reader.hasNext()) {
k = reader.nextName();
switch (k) {
case "k":
xxx;
break;
case "v":
xxx;
break;
case "p":
xxx;
break;
case "m":
xxx;
break;
case "l":
xxx;
break;
default:
reader.nextString();
break;
}
}
reader.endObject();
}
reader.endArray();
}else if("List".equals(rootName)) {
reader.beginArray();
while (reader.hasNext()) {
System.out.println(reader.nextString());
}
reader.endArray();
}else {
reader.skipValue();
}
}
常用方法如下所示:
方法名 | 返回值 | 描述 |
---|---|---|
beginArray() | void | 使用JSON流中的下一个令牌,并断言它是新数组的开始。 |
endArray() | void | 使用JSON流中的下一个令牌,并断言它是当前数组的结尾。 |
beginObject() | void | 使用JSON流中的下一个令牌,并断言它是新对象的开始。 |
endObject() | void | 使用JSON流中的下一个令牌,并断言它是当前对象的结尾。 |
close() | void | 关闭此 JSON阅读器 和底层 Reader. |
getPath() | String | 返回JSON值中当前位置的JsonPath。 |
hasNext() | Boolean | 如果当前数组或对象有其他元素,则返回true。 |
isLenient() | Boolean | 如果此解析器在接受的内容上是宽松的,则返回true。 |
setLenient(boolean lenient) | void | 将此解析器配置为在其接受的内容上宽松。 |
nextBoolean() | boolean | 返回boolean下一个令牌的值,并使用它。 |
nextDouble() | double | 返回double下一个令牌的值,并使用它。 |
nextInt() | int | 返回int下一个令牌的值,并使用它。 |
nextLong() | long | 返回long下一个令牌的值,并使用它。 |
nextName() | String | 返回下一个标记,即属性名,并使用它。 |
nextNull() | void | 使用JSON流中的下一个令牌,并断言它是文本null。 |
nextString() | String | 返回使用下一个标记的字符串值。 |
peek() | JsonToken | 返回下一个令牌的类型,而不使用它 |
skipValue() | void | 递归跳过下一个值。 |
通过使用JsonReader,现在我解析几十兆的文件基本没有问题(上百兆的还没尝试过),一个44.5M的JSON文件在4秒就能够处理完。
来源:https://blog.csdn.net/lmchhh/article/details/124613300


猜你喜欢
- Java * 代理设计模式定义:为其他对象提供一种代理以控制对这个对象的访问。 * 使用java * 机制以巧妙的方式实现了代理模式的
- 本文实例讲述了Android编程开发之Spinner组件用法。分享给大家供大家参考,具体如下:Spinner组件组要用显示一个下拉列表,在使
- C++11中的std::packaged_task是个模板类。std::packaged_task包装任何可调用目标(函数、lambda表达
- @RequestParam设置默认可以传空值设置如下@RequestParam(value="CbqkJson[]",r
- 安装JDK 向导进行相关参数设置。如图: 正在安装程序的相关功能,如图: 选择安装的路径,可以自定义,也可以默认路径。如图: 成功安装之
- state:比较常用,各种状态都可以用它,但是它更着重于一种心理状态或者物理状态。Status:用在人的身上一般是其身份和地位,作“状态,情
- 前言Android作为一个通用的移动平台,其首要的功能就是通话、短信以及上网等通信功能。那么,从系统的角度来看,Android究竟是怎么实现
- 我们先假设一个场景想象一下,当一个项目出现bug的时候,恰巧这个时候需要你去修改,而当你打开项目之后,眼前的代码让你有一种特别严重的陌生感,
- 走马灯是一种常见的效果,本文讲一下如何用 PageView 在 Flutter 里实现一个走马灯, 效果如下,当前页面的高度比其它页面高,切
- 一、简介在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,
- 1.用intellij idea 创建了一个springboot的项目,前期都运行的好好的,在ide中可以正常运行,但是打包成Jar运行却一
- 如何从C#获取字符串中汉字的个数?C#中使用正则表达式来从字符串中判断出汉字,然后计数,从而得到字符串中的汉字个数。先看这段代码://首先引
- 1.通过用FTP进行上传文件,首先要实现建立FTP连接,一般建立FTP连接,需要知道FTP配置有关的信息。一般要在Bean中建立一个Serv
- 在传统的Java编程中,被广为人知的一个知识点是:java Interface接口中不能定义private私有方法。只允许我们定义publi
- 一、引入类型别名当配置 XML 文件,需要指明Java类型时,类型别名可替代Java类型的全名,一般会设置一个简单缩写的类型别名去替代它,用
- Properties属性文件中的值有等号和换行Spring配置Shiro的过滤器时,有个filterChainDefinitions属性,值
- 前言前面几篇我们学习的都是单表查询,就是对一张表中的数据进行查询。而实际项目中,基本都会有多张表联合查询的情况,今天我们就来了解下JPA的联
- 使用对象初始值设定项初始化对象可以使用对象初始值设定项以声明方式初始化类型对象,而无需显式调用类型的构造函数。下面的示例演示如何将对象初始值
- 在程序设计过程中,我们总是希望自己设计的程序是天衣无缝的,但这几乎又是不可能的。即使程序编译通过,同时也实现了所需要的功能,也并不代表程序就
- Spring Boot 集成MyBatis在集成MyBatis前,我们先配置一个druid数据源。Spring Boot 集成druiddr