Java實(shí)現(xiàn)解析JSON大文件JsonReader工具詳解
一,使用背景
之前遇到一個(gè)需求,是需要將一個(gè)json文件解析存儲(chǔ)到數(shù)據(jù)庫中。一開始測試的時(shí)候,json文件的大小都在幾兆以內(nèi),所以直接將json文件轉(zhuǎn)化為字符串,再轉(zhuǎn)化成JSONObject對象進(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ù)字、布爾值和空值)以及開始和 對象和數(shù)組的結(jié)束分隔符。 令牌被遍歷 深度優(yōu)先順序,與它們在 JSON 文檔中出現(xiàn)的順序相同。 在 JSON 對象中,名稱/值對由單個(gè)標(biāo)記表示。
解析json
創(chuàng)建遞歸下降解析器 JSON ,首先創(chuàng)建 創(chuàng)建一個(gè)入口點(diǎn)方法 JsonReader
.
每個(gè)對象類型和每個(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()
- 在 對象處理 方法中,首先調(diào)用 beginObject()消耗對象的左大括號(hào)。 然后創(chuàng)建一個(gè)while循環(huán)根據(jù)局部變量的名稱為其賦值。 這個(gè)循環(huán)應(yīng)該在什么時(shí)候終止 hasNext()為false。 最后,通過調(diào)用讀取對象的右括號(hào) endObject().
當(dāng)遇到嵌套對象或數(shù)組時(shí),委托給對應(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)整體對象的{,再逐個(gè)對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è)令牌,并斷言它是新對象的開始。 |
endObject() | void | 使用JSON流中的下一個(gè)令牌,并斷言它是當(dāng)前對象的結(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring中使用@Autowired注解無法注入的情況及解決
這篇文章主要介紹了spring中使用@Autowired注解無法注入的情況及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09thymeleaf實(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)容,使頁面動(dòng)態(tài)顯示,這篇文章主要介紹了thymeleaf實(shí)現(xiàn)前后端數(shù)據(jù)交換,需要的朋友可以參考下2022-07-07Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解
本文通過兩個(gè)案例來介紹下Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的相關(guān)知識(shí),每種方法通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案
今天小編就為大家分享一篇關(guān)于Java找不到或無法加載主類及編碼錯(cuò)誤問題的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02