大數據JSON流解析
背景
最近在做一個需求,需要每月從一個別的數據系統同步一次數據過來。數據量大概90W條左右,數據接口只提供了一個JSON接口,接口返回報文為JSON,並且沒有任何分頁。這個數據量如果直接使用普通方式解析的話,肯定內存溢出。
解決思路
我們要保證內存溢出,那么就不能把得的數據全部存放在內存然后處理。通常我們在處理一些大的數據文件時也會有同樣的情況,我們可能會在讀取文件的流中一行一行的對數據進行處理,處理完的數據丟棄,將會被垃圾回收,這樣一個很大的文件也可以保證正常處理。
那么對於接口,實際響應報文也是一個數據流。我們是否可以邊獲取數據流,別解析JSON呢?
FastJson:JSONReader我們首先看這個Reader的構造方法,正好是傳入一個流,正好符合我們要求。publicclass JSONReader implements Closeable {
-
private final DefaultJSONParser parser; private JSONStreamContext context; //注意這個構造方法,傳入的數據對象是一個字符流 public JSONReader(Reader reader){ this(reader, new Feature[0]); } ..... }
-
數據讀取,有多個類似readString這樣的方法去讀取數據。
//讀取一個String的數據 public String readString() { Object object; if (context == null) { object = parser.parse(); } else { readBefore(); JSONLexer lexer = parser.lexer; if (context.state == JSONStreamContext.StartObject && lexer.token() == JSONToken.IDENTIFIER) { object = lexer.stringVal(); lexer.nextToken(); } else { object = parser.parse(); } readAfter(); } return TypeUtils.castToString(object); }
3. 如何使用
-
假設我們有這樣一個json的流
{ "result":[ { "name":"張三", "age":20, "amt":10129.06 }, { "name":"李四", "age":20, "amt":10129.06 } ], "status":"success", "message":"操作成功" }
那么我們可以這么解析
String jsonStr = "{\"result\":[{\"name\":\"張三\",\"age\":20,\"amt\":10129.06},{\"name\":\"李四\",\"age\":20,\"amt\":10129.06}],\"status\":\"success\",\"message\":\"操作成功\"}"; StringReader stringReader = new StringReader(jsonStr); JSONReader jsonReader = new JSONReader(stringReader); //相當於開始讀整個json的Object對象。 jsonReader.startObject(); while (jsonReader.hasNext()) { String elem = jsonReader.readString(); System.out.println(elem); //這么判斷是為了防止對象順序會亂,如果result,status等的順序固定不需要判斷 if ("result".equals(elem)) { jsonReader.startArray(); while (jsonReader.hasNext()) { jsonReader.startObject(); while (jsonReader.hasNext()) { //這里我把所有value按照Object來統一處理.當然也可以根據實際類型調用其他方法 String itemKey = jsonReader.readString(); System.out.println(itemKey); Object o = jsonReader.readObject(); String itemValue = null; if (o != null) { itemValue = String.valueOf(o); } System.out.println(itemValue); } jsonReader.endObject(); } jsonReader.endArray(); } else if ("status".equals(elem)) { String s = jsonReader.readString(); System.out.println(s); } else { //不需要的數據,也必須讀,可以不做處理 jsonReader.readString(); } } jsonReader.endObject();
輸出結果
result name 張三 age 20 amt 10129.06 name 李四 age 20 amt 10129.06 status success message
數據從接口獲得
下面就是使用接口訪問時,流解析的過程。一邊解析一邊處理就可以保證數據用完被垃圾回收。
private int readFromUrl(String url,String month) { try { logger.info("請求地址:" + url); URL destURL = new URL(url); HttpURLConnection urlConn = (HttpURLConnection) destURL.openConnection(); urlConn.setRequestProperty("Content-Type", "text/x-www-form-urlencoded; charset=utf-8"); urlConn.setDoOutput(true); urlConn.setDoInput(true); urlConn.setConnectTimeout(300000);//300秒連接時間 urlConn.setReadTimeout(3000000);//3000秒讀取時間,數據量大可以設置長些 urlConn.setAllowUserInteraction(false); urlConn.setUseCaches(false); urlConn.setRequestMethod("GET"); int responseCode = urlConn.getResponseCode(); if (responseCode != 200) { logger.error("請求失敗,responseCode:" + responseCode); throw new RuntimeException("請求失敗,responseCode:" + responseCode); } //開始解析數據流 BufferedInputStream is = new BufferedInputStream(urlConn.getInputStream()); BufferedReader br = new BufferedReader(new InputStreamReader(is,"utf-8")); JSONReader jsonReader = new JSONReader(br); //解析並保存。這里就可以邊解析邊處理了。 int saveCount = parseJsonAndSave(jsonReader, month); is.close(); br.close(); jsonReader.close(); return saveCount; } catch (Exception e) { logger.error("請求數據同步接口失敗:",e); throw new RuntimeException("請求數據同步接口失敗",e); } }
總結
JSONReader實現基本就是按照文本順序往下讀,比如startObject馬上讀取是否是”{“。這樣將一個整體的大塊數據,變成了一個個字符讀取,除了解決了內存問題,其實用它解析json效率也是比一般方法高很多。
轉載:大數據JSON流解析 https://blog.csdn.net/xbliu564/article/details/78944755