一、问题布景
我们的意图是从动态生成的json文件中读取信息,暂且叫它foo.json吧。
因为前期开发时,发现生成的foo.json都比较小(50kb左右,大的也不过几百kb不超越1M),因此在解析这个json文件时,就直接把它整个读取到内存中,再经过fastjson对json数据处理。雷就此埋下了。。。
二、案发现场
当同事跟我反映项目出问题了,接口回来的数据不正常。便去看运行日志,发现了这么一条记载:
java.lang.OutOfMemoryError:Java heap space
当时百思不得其解,到底是哪里形成的堆内存反常呢?因为是第一次在企业项目遇到这个问题,抱着斗胆猜测小心验证的情绪,最终发现居然是因为生成的foo.json太大了(接近600M,这是纯文本呀,大家能够幻想下有多少数据)。找到问题原因后,就好处理了。
已然一次性读取,内存遭不住,那就只好分批读取咯。
三、处理方案
- 运用缓存:运用缓存技术提高读取功率,例如将读取到的数据暂时存储到一个缓存区中,待缓存区到达一定巨细后再一次性解析缓存中存储的 JSON 数据。
- 运用流式读取:选用流式读取方法,即边读边解析,防止一次性将整个文件全部读入内存。这种方法需求凭借 JsonReader 这样支撑流式读取的东西类,能够大幅降低内存占用和读取速度。
第一种思路是对的,可是因为是json格局的数据,那么对格局是有要求的。比方某个json目标比较大,缓存区只存了它一部分的内容,这个部分数据想要解析就要另写逻辑了。因此这里重点介绍Gson的JsonReader。
核心API介绍:
-
beginArray()
:读取一个数组的开端符号[
。 -
endArray()
:读取一个数组的结束符号]
。 -
beginObject()
:读取一个目标的开端符号{
。 -
endObject()
:读取一个目标的结束符号}
。 -
hasNext()
:判断当时方位之后是否还有更多的元素,并回来true
或false
。 -
nextName()
:读取一个JSON目标中的字段名并回来这个字段名的字符串形式,假如这个字段不存在则回来null。 -
nextBoolean()
:读取下一个JSON值并将其解析为布尔类型,假如这个JSON值不是布尔类型则抛出一个IOException。 -
nextDouble()
:读取下一个JSON值并将其解析为双精度浮点数类型,假如这个JSON值不是数字类型则抛出一个IOException。 -
nextString()
:读取下一个JSON值并将其解析为字符串类型。 -
skipValue()
:越过当时JSON元素,以便在读取无需处理的JSON文件时快速行进。
举例:
foo.json:
{
"version_major": 1,
"version_minor": 27,
"version_patch": 0,
pipelines": [
"build",
"test",
"report"
]
}
JsonReader reader = new JsonReader(new FileReader("foo.json"));
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
if (key.equals("pipelines")) {
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject();
while (reader.hasNext()) {
System.out.println(reader.nextName());
}
reader.endObject();
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
这样就把pipelines
数组中的元素读取出来啦。当然这仅仅个示例,假如你的json格局够复杂。那么你会发现代码中while
和if
嵌套的层次就会很深。这个时候选用递归的思路或许能够处理你的问题。
最终:主张大家运用Gson的JsonReader,而不用FastJson的JSONReader。经过测试发现FastJson的JSONReader虽然也是流式读取,可是对内存的占用依然很高。关于我们不想要的数据Gson供给skipValue()
越过,而FastJson只能经过readObject()
越过。