大数据JSON流解析
背景
最近在做一个需求,需要每月从一个别的数据系统同步一次数据过来。数据量大概90W条左右,数据接口只提供了一个JSON接口,接口返回报文为JSON,并且没有任何分页。这个数据量如果直接使用普通方式解析的话,肯定内存溢出。
解决思路
我们要保证内存溢出,那么就不能把得的数据全部存放在内存然后处理。通常我们在处理一些大的数据文件时也会有同样的情况,我们可能会在读取文件的流中一行一行的对数据进行处理,处理完的数据丢弃,将会被垃圾回收,这样一个很大的文件也可以保证正常处理。
那么对于接口,实际响应报文也是一个数据流。我们是否可以边获取数据流,别解析JSON呢?
FastJson:JSONReader
我们首先看这个Reader的构造方法,正好是传入一个流,正好符合我们要求。
public class 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); }
- 如何使用
假设我们有这样一个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效率也是比一般方法高很多。