Json海量數據解析
前言
? 在android開發中,app和服務器進行數據傳輸時大多數會用到json。在解析json中通常會用到以下幾種主流的解析庫:jackson、gson、fastjson。而對于從server端獲取的數據量很小時候,我們可能會忽略解析所產生的性能問題。而我在開發的過程中就碰到因為解析json而產生嚴重的問題。
問題場景
先描述以下問題的場景:app做收銀庫存管理。這時候每次登陸時候會去服務端同步所有的商品、分類等數據。而這時候,當商品的數量很大的時候,客戶端拿到數據時候對app來說還是比較大的。而server端是將所有的數據序列化為json字符串存入到文件,然后app去下載文件并進行解析。下面說下我的修改歷程。
踩坑過程
- 第一版代碼是直接講文件讀出為字符串,使用gson直接反序列化
new Gson().fromJson(String s,Type type)
這時候OOM,查看日志,發現文件讀出字符串時候直接OOM了(當初并沒有考慮會有這么大的數據,暈倒)。從server端下載下來的文件就有20M左右。 - 第二版代碼使用FastJson的JSONReader。對每個對象進行單獨序列化。也就是下面講到的fastjson方法1。這時候OOM問題的解決了。因為是讀的文件流,邊讀邊解析數據。基本解決了問題。但通過Android Studio的Monitors發現,解析時候內存不斷的在被消耗(汗。。還好沒有爆掉)。
- 第三版代碼使用Fastjson的JSONReader。對每個json的每個key每個value都單獨的解析和讀取。也就是下面講到的fastjson方法2。這時候所有的性能問題全部解決,速度最快,幾乎沒有消耗多少內存。
? 上面是我一步步走過得坑,唉。可能對于看過fastjson源碼的童鞋來說so easy。但第一次碰到后,坑還是得一步步的踩。當然也是要不斷的通過看源碼、寫測試代碼、比較內存和時間。下面是我做的一些測試。
測試驗證
準備工作
-
相關依賴庫
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.29' // https://mvnrepository.com/artifact/commons-io/commons-io compile group: 'commons-io', name: 'commons-io', version: '2.5' // https://mvnrepository.com/artifact/com.google.code.gson/gson compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0's
-
測試數據生成
public void createDataFile() { List<Good> goodList = new ArrayList<>(); for (int i = 0; i < 200000; i++) { Good good = new Good(System.currentTimeMillis() + "_" + i, new String("booke") + i, 10.f + i, System.currentTimeMillis() + "", "describe book" + i, i); goodList.add(good); } try { String json = JSONArray.toJSONString(goodList); FileUtils.write(new File("e://goods.json"), json, "UTF-8"); } catch (IOException e) { log("" + e.getMessage()); e.printStackTrace(); } }
結果分析
-
gson解析
使用流進行讀取。20W條數據,內存不斷的被消耗。兩次解析時間為 50,488ms、48,940ms 性能是相當的差
List<Good> list = new Gson().fromJson(new InputStreamReader(getAssets().open("goods.json"),
"UTF-8"), new TypeToken<List<Good>>() {}.getType());
1.png
- fastjson方法1
使用流進行讀取。內存也是不斷被消耗。三次解析時間為 33,394ms 31,632ms 32,378ms
JSONReader reader = new JSONReader(new InputStreamReader(getAssets().open("goods.json"),
"UTF-8"));
reader.startArray();
while (reader.hasNext()) {
Good good = reader.readObject(Good.class);
}
reader.endArray();
reader.close();
reader = null;
2.png
-
fastjson方法2
使用流進行讀取,每個key和value自己來處理。三次解析時間為 31,242ms 31,583ms 30,834ms。同時,內存幾乎沒有太多的占用,比較的平穩。這個方法當然最優。
JSONReader reader = new JSONReader(new InputStreamReader(getAssets().open("goods.json"),
"UTF-8"));
reader.startArray();
while (reader.hasNext()) {
reader.startObject();
Good good = new Good();
while (reader.hasNext()) {
String key = reader.readString();
if ("id".equals(key)) {
good.setId(reader.readString());
} else if ("name".equals(key)) {
good.setName(reader.readString());
} else if ("price".equals(key)) {
good.setPrice(Double.parseDouble(reader.readString()));
} else if ("barCode".equals(key)) {
good.setBarCode(reader.readString());
} else if ("desc".equals(key)) {
good.setDesc(reader.readString());
} else if ("count".equals(key)) {
good.setCount(Integer.parseInt(reader.readString()));
} else {
reader.readObject();
}
}
reader.endObject();
}
reader.endArray();
reader.close();
reader = null;
3.png
最后我們對比消耗時間
5.png
其他
- Good.java
public class Good {
private String id;
private String name;
private double price;
private String barCode;
private String desc;
private int count;
public Good() {
}
public Good(String id, String name, double price, String barCode, String desc, int count) {
this.id = id;
this.name = name;
this.price = price;
this.barCode = barCode;
this.desc = desc;
this.count = count;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getBarCode() {
return barCode;
}
public void setBarCode(String barCode) {
this.barCode = barCode;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Good{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", price=" + price +
", barCode='" + barCode + '\'' +
", desc='" + desc + '\'' +
", count=" + count +
'}';
}
}