Json.NET 缓存类型的序列化信息吗?



在.NET世界中,当涉及到对象序列化时,通常会在运行时检查对象的字段和属性。对此作业使用反射通常很慢,并且在处理大型对象集时是不可取的。另一种方法是使用IL发射或构建表达式树,这些表达式树在反射方面提供了显著的性能增益。后者是大多数现代库在处理序列化时选择的。然而,在运行时构建和发出IL需要时间,并且只有缓存这些信息并将其重新用于相同类型的对象时,投资才会得到回报。

在使用Json.NET时,我不清楚使用了上面描述的哪种方法,如果确实使用了后者,是否使用了缓存。

例如,当我这样做时:

JsonConvert.SerializeObject(new Foo { value = 1 });

Json.NET是否构建Foo的成员访问信息和缓存以便以后重用?

是的Json.NET在其IContractResolverDefaultContractResolverCamelCasePropertyNamesContractResolver中缓存类型序列化信息。除非指定自定义协定冲突解决程序,否则会缓存并重用这些信息。

对于DefaultContractResolver,在内部维护一个全局静态实例,每当应用程序没有指定自己的协定解析程序时,Json.NET就会使用该实例。另一方面,CamelCasePropertyNamesContractResolver维护在所有实例之间共享的静态表。(我认为这种不一致性源于遗留问题;请参阅此处了解详细信息。)

这两种类型都被设计为完全线程安全的,因此线程之间的共享应该不会成为问题。

如果您选择实现和实例化自己的契约解析程序,那么只有在缓存和重用契约解析程序实例本身的情况下,类型信息才会被缓存和重用。因此,Newtonsoft建议:

为了提高性能,您应该创建一次契约解析程序,并在可能的情况下重用实例。解析合约很慢,IContractResolver的实现通常会缓存合约。

如果内存消耗是一个问题并且无论出于何种原因,您都需要最小化缓存合约永久占用的内存,您可以构建自己的DefaultContractResolver本地实例(或某些自定义子类),使用该实例进行序列化,然后立即删除对它的所有引用,例如:

public class JsonExtensions
{
    public static string SerializeObjectNoCache<T>(T obj, JsonSerializerSettings settings = null)
    {
        settings = settings ?? new JsonSerializerSettings();
        bool reset = (settings.ContractResolver == null);
        if (reset)
            // To reduce memory footprint, do not cache contract information in the global contract resolver.
            settings.ContractResolver = new DefaultContractResolver();
        try
        {
            return JsonConvert.SerializeObject(obj, settings);
        }
        finally
        {
            if (reset)
                settings.ContractResolver = null;
        }
    }
}

如果您正在使用CamelCasePropertyNamesContractResolver,请使用适当的命名策略切换到DefaultContractResolver,例如:

settings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };

缓存的大部分约定内存(但不是全部)最终将被垃圾回收。当然,这样做,序列化性能可能会受到严重影响。(一些包含反映的信息的表,例如enum类型和数据契约属性是全局共享的,而不是回收的。)

有关更多信息请参阅Newtonsoft的性能提示:重用合同解析程序。

最新更新