【问题标题】:Out of memory error while parsing a large JSON using Jackson library on Android在 Android 上使用 Jackson 库解析大型 JSON 时出现内存不足错误
【发布时间】:2013-01-14 17:45:38
【问题描述】:

我正在使用 Jackson 库来解析来自服务器的大型 JSON 响应。 json 的大小约为 7-8 mb。

我在这段代码中遇到了 outOfMemoryError:

ObjectMapper mapper = new ObjectMapper();
JsonNode rootParser = mapper.readValue(is, JsonNode.class);

这是我得到的例外:

    01-14 13:13:20.103: E/AndroidRuntime(25468): FATAL EXCEPTION: Thread-13
    01-14 13:13:20.103: E/AndroidRuntime(25468): java.lang.OutOfMemoryError
    01-14 13:13:20.103: E/AndroidRuntime(25468): at java.util.ArrayList.add(ArrayList.java:123)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.node.ArrayNode._add(ArrayNode.java:722)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.node.ArrayNode.add(ArrayNode.java:203)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeArray(JsonNodeDeserializer.java:224)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:200)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeArray(JsonNodeDeserializer.java:224)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:200)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeArray(JsonNodeDeserializer.java:224)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:200)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:197)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeArray(JsonNodeDeserializer.java:224)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:200)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:197)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:58)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:15)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2732)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1909)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at com.sarla.smartglance.communication.JsonDecoder.decodeResponse(JsonDecoder.java:87)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at com.sarla.smartglance.communication.JsonDecoder.decode(JsonDecoder.java:68)
01-14 13:13:20.103: E/AndroidRuntime(25468):    at com.sarla.smartglance.communication.MHttpManager$1.run(MHttpManager.java:86)

我尝试了所有方法,但找不到任何解决方案来解析安卓上如此大量的数据。

【问题讨论】:

  • 您是否尝试过只使用.readTree() 而不是读入JsonNode
  • 试一试gson
  • ArrayList 上的数据有多大,听起来很大。
  • Gson 无济于事——树模型是重量级的,并且在不同的库上大小大致相同。

标签: java android json jackson out-of-memory


【解决方案1】:

对于 7 到 8 兆的 JSON,您使用的树模型通常会使用 20 到 50 兆的内存(对于 XML 和 JSON,dom 模型的大小是 3 到 5 倍)。无论使用哪种库,您都无能为力:它们都使用 Lists 和 Maps 构建树,这是一种重量级的方法。

相反,您应该考虑使用普通旧 Java 对象 (POJO),这将使用更少的内存。为此,您需要建模与您的 JSON 结构匹配的 POJO;在不知道结构的情况下我不能举个例子(如果你在问题上添加示例,我可以),但是要解析的代码类似于另一个答案引用的 GSON 代码:

MyValue value = mapper.readValue(json, MyValue.class);

这将适用于 Jackson 以及许多其他 Java JSON 库(至少是 Gson、Genson),并且使用起来也更快。 JSON 树本质上是昂贵且重量级的,不能用于数兆字节的内容。

最后,如果您的输入由一系列项目组成,那么还有更好的方法将其分割(无论单个项目是 JsonNodes 还是 POJO 都可以这样做!)。不过不知道你的内容是不是这样。

【讨论】:

  • 仍然无法理解为什么人们使用这么多 DOM api...我想他们甚至不直接在这些对象上工作,所以仍然需要将其转换为 POJO...如果只是一小部分需要json流,以减少内存中的数据可以使用jacksons流api。
  • 非常感谢 StaxMan。我确实注意到它消耗了 20-30 MB 的内存,但我无法减少它。对于基于 POJO 的模型,我必须更改整个解析结构。我的输入有点复杂,因为它是分析数据。因此,如果这是唯一的方法,那么我想我必须这样做。
  • 另一种可能性是,如果(且仅当)模型的一部分是事物的序列(列表/数组),您也可以使用增量解析。如果是这样,要么需要使用ObjectMapper.readValues() 或原始Streaming API (JsonParser) 进行迭代,然后readValueAs() 位于子时间的根。
【解决方案2】:

我们在这里使用 gson 库,通过上面的代码,我们可以毫无问题地获取大于 50Mb 的文件:

public static <T extends Object> T readFile(String caminho_arquivo, Type type) {

    GsonBuilder gson_builder = new GsonBuilder();

    final SimpleDateFormat sdf_date     = new SimpleDateFormat("yyyy-MM-dd");
    final SimpleDateFormat sdf_datetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    gson_builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>(){

        @Override
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

            try {
                if (json.getAsJsonPrimitive().getAsString().length() == 10)
                    return sdf_date.parse(json.getAsJsonPrimitive().getAsString());
                else
                    return sdf_datetime.parse(json.getAsJsonPrimitive().getAsString());

            } catch (ParseException e) {
                Log.e("JSON", "Erro na deserialização de datas no JSON: " + json.getAsJsonPrimitive().getAsString());
                return null;
            }
        }

    });

    Gson gson = gson_builder.create();

    File fileJSON = new File(caminho_arquivo);

    FileReader reader = null;

    try {
        reader = new FileReader(fileJSON);

        return gson.fromJson(reader, type);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            reader.close();

            if (fileJSON.exists())
                fileJSON.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return null;
}

试试这个库,很好,我们只在服务器端使用jackson,因为jackson在Android中比gson慢,至少在我们的测试中。

【讨论】:

  • 上面的代码有问题——没有指定用于FileReader的字符编码,所以在非ASCII输入时可能会出错。但是应该没有必要构造一个Reader, just pass in InputStream`。
  • 此外,性能测试的链接会很棒——唯一经常引用的链接 (martinadamek.com/2011/02/04/…) 并没有显示 Gson 更快。然而,Gson 确实有更快的启动时间。并且 2.1 版本还可以(早期版本非常慢)。
  • 嗯,它不是错误的,它对我来说工作得很好,我有特殊字符没有问题。 “caminho_arquivo”是文件在 SDCard 中的位置。我使用它是因为我从服务器获取了一个压缩文件,之后我解压缩文件并获取 .json 文件...
  • 当我需要解析大文件时,Jackson 帮不了我,所以对于我的应用,GSON 是最好的,甚至更慢,可以读取大文件。
  • 它可能适用于您的平台;但它很容易在别人身上失败;因此它被打破了。您应该只指定编码 (UTF-8)。这是 Java 开发人员常犯的错误,所以也许你只是从某个地方剪切粘贴了它。不是侮辱,虽然措辞可能很强烈。
【解决方案3】:

试用并使用:

JsonNode rootParser = mapper.readTree(is);

改为。

【讨论】:

    猜你喜欢
    • 2014-05-10
    • 1970-01-01
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-07
    相关资源
    最近更新 更多