Java實(shí)現(xiàn)解析JSON大文件JsonReader工具詳解
一,使用背景
之前遇到一個(gè)需求,是需要將一個(gè)json文件解析存儲(chǔ)到數(shù)據(jù)庫(kù)中。一開始測(cè)試的時(shí)候,json文件的大小都在幾兆以內(nèi),所以直接將json文件轉(zhuǎn)化為字符串,再轉(zhuǎn)化成JSONObject對(duì)象進(jìn)行處理時(shí)不會(huì)出現(xiàn)問題,如下所示:
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);
}
但是,當(dāng)出現(xiàn)幾十兆文件的時(shí)候,這時(shí)候就會(huì)報(bào)出內(nèi)存溢出的錯(cuò)誤
java.lang.OutOfMemoryError: Java heap space
雖然稍微大一點(diǎn)的文件,可以通過調(diào)整JVM參數(shù)來解決,如下所示
-Xms512m -Xmx2048m
但是這畢竟不是最合理的方法,因?yàn)楫?dāng)文件大到一定程度后,字節(jié)數(shù)組和字符串類型都存在接收不了的情況。因此,只能選擇另外的方式,此時(shí),Google的JsonReader是一個(gè)不錯(cuò)的解決方案。
二,JsonReader的使用
maven依賴如下:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
JsonReader讀取 JSON (RFC 7159) 編碼值作為令牌流。 此流包括文字 值(字符串、數(shù)字、布爾值和空值)以及開始和 對(duì)象和數(shù)組的結(jié)束分隔符。 令牌被遍歷 深度優(yōu)先順序,與它們?cè)?JSON 文檔中出現(xiàn)的順序相同。 在 JSON 對(duì)象中,名稱/值對(duì)由單個(gè)標(biāo)記表示。
解析json
創(chuàng)建遞歸下降解析器 JSON ,首先創(chuàng)建 創(chuàng)建一個(gè)入口點(diǎn)方法 JsonReader.
每個(gè)對(duì)象類型和每個(gè)數(shù)組類型都需要一個(gè)方法。
- 在 數(shù)組處理 方法中,首先調(diào)用 beginArray()消耗數(shù)組的左括號(hào)。 然后創(chuàng)建一個(gè)累積值的while循環(huán),在何時(shí)終止 hasNext()為false。 最后,通過調(diào)用讀取數(shù)組的右括號(hào) endArray()
- 在 對(duì)象處理 方法中,首先調(diào)用 beginObject()消耗對(duì)象的左大括號(hào)。 然后創(chuàng)建一個(gè)while循環(huán)根據(jù)局部變量的名稱為其賦值。 這個(gè)循環(huán)應(yīng)該在什么時(shí)候終止 hasNext()為false。 最后,通過調(diào)用讀取對(duì)象的右括號(hào) endObject().
當(dāng)遇到嵌套對(duì)象或數(shù)組時(shí),委托給對(duì)應(yīng)的處理方法。
當(dāng)遇到未知名稱時(shí),嚴(yán)格的解析器應(yīng)該失敗并返回。 但寬松的解析器應(yīng)該調(diào)用 skipValue()遞歸地 跳過值的嵌套標(biāo)記,否則可能會(huì)發(fā)生沖突。
如果一個(gè)值可能為空,應(yīng)該首先檢查使用 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", ...]
}
那按照J(rèn)sonReader解析的思路,我應(yīng)該先消費(fèi)整體對(duì)象的{,再逐個(gè)對(duì)INFO,ATTR,Parms,List進(jìn)行處理,總而言之,就是
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流中的下一個(gè)令牌,并斷言它是新數(shù)組的開始。 |
| endArray() | void | 使用JSON流中的下一個(gè)令牌,并斷言它是當(dāng)前數(shù)組的結(jié)尾。 |
| beginObject() | void | 使用JSON流中的下一個(gè)令牌,并斷言它是新對(duì)象的開始。 |
| endObject() | void | 使用JSON流中的下一個(gè)令牌,并斷言它是當(dāng)前對(duì)象的結(jié)尾。 |
| close() | void | 關(guān)閉此 JSON閱讀器 和底層 Reader. |
| getPath() | String | 返回JSON值中當(dāng)前位置的JsonPath。 |
| hasNext() | Boolean | 如果當(dāng)前數(shù)組或?qū)ο笥衅渌?,則返回true。 |
| isLenient() | Boolean | 如果此解析器在接受的內(nèi)容上是寬松的,則返回true。 |
| setLenient(boolean lenient) | void | 將此解析器配置為在其接受的內(nèi)容上寬松。 |
| nextBoolean() | boolean | 返回boolean下一個(gè)令牌的值,并使用它。 |
| nextDouble() | double | 返回double下一個(gè)令牌的值,并使用它。 |
| nextInt() | int | 返回int下一個(gè)令牌的值,并使用它。 |
| nextLong() | long | 返回long下一個(gè)令牌的值,并使用它。 |
| nextName() | String | 返回下一個(gè)標(biāo)記,即屬性名,并使用它。 |
| nextNull() | void | 使用JSON流中的下一個(gè)令牌,并斷言它是文本null。 |
| nextString() | String | 返回使用下一個(gè)標(biāo)記的字符串值。 |
| peek() | JsonToken | 返回下一個(gè)令牌的類型,而不使用它 |
| skipValue() | void | 遞歸跳過下一個(gè)值。 |
通過使用JsonReader,現(xiàn)在我解析幾十兆的文件基本沒有問題(上百兆的還沒嘗試過),一個(gè)44.5M的JSON文件在4秒就能夠處理完。
到此這篇關(guān)于Java實(shí)現(xiàn)解析JSON大文件JsonReader工具詳解的文章就介紹到這了,更多相關(guān)Java JsonReader內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring中使用@Autowired注解無法注入的情況及解決
這篇文章主要介紹了spring中使用@Autowired注解無法注入的情況及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java對(duì)接支付寶支付接口簡(jiǎn)單步驟記錄
最近項(xiàng)目APP需要接入微信、支付寶支付功能,在分配開發(fā)任務(wù)時(shí),聽說微信支付接口比支付寶支付接口要難實(shí)現(xiàn),這篇文章主要給大家介紹了關(guān)于java對(duì)接支付寶支付接口的簡(jiǎn)單步驟,需要的朋友可以參考下2024-05-05
Springboot 接口對(duì)接文件及對(duì)象的操作方法
這篇文章主要介紹了Springboot 接口對(duì)接文件及對(duì)象的操作,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
thymeleaf實(shí)現(xiàn)前后端數(shù)據(jù)交換的示例詳解
Thymeleaf?是一款用于渲染?XML/XHTML/HTML5?內(nèi)容的模板引擎,當(dāng)通過?Web?應(yīng)用程序訪問時(shí),Thymeleaf?會(huì)動(dòng)態(tài)地替換掉靜態(tài)內(nèi)容,使頁(yè)面動(dòng)態(tài)顯示,這篇文章主要介紹了thymeleaf實(shí)現(xiàn)前后端數(shù)據(jù)交換,需要的朋友可以參考下2022-07-07
Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解
本文通過兩個(gè)案例來介紹下Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的相關(guān)知識(shí),每種方法通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
淺談java 面對(duì)對(duì)象(抽象 繼承 接口 多態(tài))
下面小編就為大家?guī)硪黄獪\談java 面對(duì)對(duì)象(抽象 繼承 接口 多態(tài))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案
今天小編就為大家分享一篇關(guān)于Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02

