【问题标题】:Jackson deserializing with custom deserializer causes a lot of GC calls and takes a lot longerJackson 使用自定义反序列化器进行反序列化会导致大量 GC 调用并花费更长的时间
【发布时间】:2012-11-01 10:46:28
【问题描述】:

为了解决我在in this thread 讨论的类型不匹配问题,我创建了自定义Deserializers 并将它们添加到ObjectMapper。但是,性能会因此而显着下降。

使用默认反序列化器,我在logcat 中获得 1-2 次垃圾收集调用,而使用自定义反序列化器至少有 7-8 次 GC 调用,因此处理时间也显着增加。

我的反序列化器:

public class Deserializer<T> {

public JsonDeserializer<T> getDeserializer(final Class<T> cls) {
  return new JsonDeserializer<T> (){

     @Override
     public T deserialize(JsonParser jp, DeserializationContext arg1) throws IOException, JsonProcessingException {
        JsonNode node = jp.readValueAsTree();
        if (node.isObject()) {
          return new ObjectMapper().convertValue(node, cls);
        }
        return null;
     }
 };
}
} 

我正在使用它来添加到 Mapper

public class DeserializerAttachedMapper<T> {

   public ObjectMapper getMapperAttachedWith(final Class<T> cls , JsonDeserializer<T> deserializer) {
      ObjectMapper mapper = new ObjectMapper();
      SimpleModule module = new SimpleModule(deserializer.toString(), new Version(1, 0, 0, null, null, null));
      module.addDeserializer(cls, deserializer);
      mapper.registerModule(module);
      return mapper;
   }
}

编辑:添加额外数据

我的 JSON 相当大,但不是很大: 我已经粘贴了it here

如果我使用此代码,现在解析相同的 JSON:

   String response = ConnectionManager.doGet(mAuthType, url, authToken);
    FLog.d("location object response" + response);
  //        SimpleModule module = new SimpleModule("UserModule", new Version(1, 0, 0, null, null, null));
  //        JsonDeserializer<User> userDeserializer = new Deserializer<User>().getDeserializer(User.class);     
  //        module.addDeserializer(User.class, userDeserializer);

    ObjectMapper mapper = new ObjectMapper();
  //        mapper.registerModule(module);
    JsonNode tree = mapper.readTree(response);
    Integer code = Integer.parseInt(tree.get("code").asText().trim());

    if(Constants.API_RESPONSE_SUCCESS_CODE == code) {
        ExploreLocationObject locationObject = mapper.convertValue(tree.path("response").get("locationObject"), ExploreLocationObject.class);
        FLog.d("locationObject" + locationObject);
        FLog.d("locationObject events" + locationObject.getEvents().size());
        return locationObject;
    }       
    return null;    

那我的logcat是like this

但如果我将此代码用于相同的 JSON

        String response = ConnectionManager.doGet(mAuthType, url, authToken);
    FLog.d("location object response" + response);
    SimpleModule module = new SimpleModule("UserModule", new Version(1, 0, 0, null, null, null));
    JsonDeserializer<User> userDeserializer = new Deserializer<User>().getDeserializer(User.class);

    module.addDeserializer(User.class, userDeserializer);
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    JsonNode tree = mapper.readTree(response);
    Integer code = Integer.parseInt(tree.get("code").asText().trim());

    if(Constants.API_RESPONSE_SUCCESS_CODE == code) {
        ExploreLocationObject locationObject = mapper.convertValue(tree.path("response").get("locationObject"), ExploreLocationObject.class);
        FLog.d("locationObject" + locationObject);
        FLog.d("locationObject events" + locationObject.getEvents().size());
        return locationObject;
    }       
    return null;        

那我的logcat是like this

【问题讨论】:

  • 补充一点:确保你重用了ObjectMapper——它是一个重量级的对象,不应该在每个请求中创建一次。否则它肯定会导致大量的 GC 活动。仅从上面的代码很难确定。
  • 实际上我注意到您的反序列化器创建了一个 ObjectMapper:这是非常昂贵的。您可以通过使用JsonParser.getCodec() 来避免这种情况,将结果转换为ObjectMapper(这是安全的向上转换)。这也应该有很大帮助。
  • 我修改了我的代码并将对象映射器添加到我的单例中,默认反序列化器的性能有所提高,但自定义反序列化器的问题仍然存在。另外,我注意到如果我将两个反序列化器添加到同一个模块,那么默认的反序列化器会被调用,而不是自定义的反序列化器。 JsonParser.getCodec() 不是静态方法,所以我必须为每个请求创建 JsonParser。不会也很贵吧??
  • 两个反序列化器的行为听起来不对;只有在没有自定义类型的情况下才应调用默认值。在JsonParser -- 不应该创建新的,但如果你在deserialize(...) 方法中,你会被传递一个(不是映射器)。所以我要说的是,如果你有JsonParserJsonGenerator(有引用;如果解析器/生成器是由ObjectMapper 创建的,则为真),你总能找到一个映射器。
  • 只是想在反序列化器中添加Jsonparser.getCodec()在这种情况下导致stackOverFlow异常,可能是因为它调用了与反序列化器相同的deserialize(JsonParser jp, DeserializationContext arg1)方法,导致无限循环。

标签: java android json jackson


【解决方案1】:

物体有多大?代码基本上构建了一个树模型(某种 dom 树),这将占用原始文档的 3 到 5 倍的内存。所以我假设你的输入是一个巨大的 JSON 文档。

您绝对可以使用 Streaming API 编写更高效的版本。比如:

JsonParser jp = mapper.getJsonFactory().createJsonParser(input);
JsonToken t = jp.nextToken();
if (t == JsonToken.START_OBJECT) {
   return mapper.readValue(jp, classToBindTo);
}
return null;

也可以通过数据绑定(如JsonDeserializer)来实现这一点,但仅仅因为你想委托给“默认”反序列化器,它变得有点复杂。 为此,您需要实现BeanDeserializerModifier,并在调用“modifyDeserializer”时替换标准反序列化器:您自己的代码可以保留对原始反序列化器的引用并委托给它,而不是使用中间树模型。

【讨论】:

  • 我已经编辑了我的问题并添加了 JSON、解析代码和日志。谢谢
【解决方案2】:

如果您不依赖于 jackson,您也可以尝试 Genson http://code.google.com/p/genson/。 在您的情况下,有两个主要优点:您不会失去性能,它应该更容易实现。如果属性事件不是以大写字母开头,则使用 @JsonProperty("Event") 对其进行注释(其他以大写字母开头的属性也是如此)。 使用以下代码,您应该完成:

Genson genson = new Genson.Builder()
            .withDeserializerFactory(new EventDeserializerFactory()).create();

YourRootClass[] bean = genson.deserialize(json, YourRootClass[].class);

class EventDeserializerFactory implements Factory<Deserializer<Event>> {

    public Deserializer<Event> create(Type type, Genson genson) {
        return new EventDeserializer(genson.getBeanDescriptorFactory().provide(Event.class,
                genson));
    }

}

class EventDeserializer implements Deserializer<Event> {
    private final Deserializer<Event> standardEventDeserializer;

    public EventDeserializer(Deserializer<Event> standardEventDeserializer) {
        this.standardEventDeserializer = standardEventDeserializer;
    }

    public Event deserialize(ObjectReader reader, Context ctx) throws TransformationException,
            IOException {
        if (ValueType.ARRAY == reader.getValueType()) {
            reader.beginArray().endArray();
            return null;
        }
        return standardEventDeserializer.deserialize(reader, ctx);
    }
}

【讨论】:

  • 感谢您的回答。一定会试一试的。但是,atm 我将首先尝试使用 jackson 提高性能。
猜你喜欢
  • 1970-01-01
  • 2017-09-10
  • 2014-08-02
  • 2018-07-15
  • 1970-01-01
  • 2021-12-13
  • 2016-03-14
  • 2021-11-07
  • 2020-05-27
相关资源
最近更新 更多