一、问题布景

我们的意图是从动态生成的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介绍:

  1. beginArray():读取一个数组的开端符号[
  2. endArray():读取一个数组的结束符号]
  3. beginObject():读取一个目标的开端符号{
  4. endObject():读取一个目标的结束符号}
  5. hasNext():判断当时方位之后是否还有更多的元素,并回来truefalse
  6. nextName():读取一个JSON目标中的字段名并回来这个字段名的字符串形式,假如这个字段不存在则回来null。
  7. nextBoolean():读取下一个JSON值并将其解析为布尔类型,假如这个JSON值不是布尔类型则抛出一个IOException。
  8. nextDouble():读取下一个JSON值并将其解析为双精度浮点数类型,假如这个JSON值不是数字类型则抛出一个IOException。
  9. nextString():读取下一个JSON值并将其解析为字符串类型。
  10. 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格局够复杂。那么你会发现代码中whileif嵌套的层次就会很深。这个时候选用递归的思路或许能够处理你的问题。

最终:主张大家运用Gson的JsonReader,而不用FastJson的JSONReader。经过测试发现FastJson的JSONReader虽然也是流式读取,可是对内存的占用依然很高。关于我们不想要的数据Gson供给skipValue()越过,而FastJson只能经过readObject()越过。