likes
comments
collection
share

奇遇记之——读取大文件导致OOM

作者站长头像
站长
· 阅读数 68

一、问题背景

我们的目的是从动态生成的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()跳过。

转载自:https://juejin.cn/post/7239225588065108029
评论
请登录