性能贴士
开箱即用的Json.NET比DataContractJsonSerializer和JavaScriptSerializer更快。此处有一些贴士,让它变得更快
- 重用契约解析器
- 优化内存使用
- JsonConverters
- 手工序列化
- 基准
重用契约解析器
IContractResolver在把.NET类型解析为序列化过程中在JsonSerializer内部使用的契约。创建契约涉及到检查反射慢的类型,因此协定通常由IContractResolver的实现(例如DefaultContractResolver)来缓存。
为了避免每次使用JsonSerializer时重新创建契约的开销,你应该创建契约解析器并重用它。注意,如果你没有使用契约解析器,则在序列化和反序列化时,自动使用共享的内部实例。
重用ContractResolver
// BAD - a new contract resolver is created each time, forcing slow reflection to be used
string json1 = JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
});
// GOOD - reuse the contract resolver from a shared location
string json2 = JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = AppSettings.SnakeCaseContractResolver
});
// GOOD - an internal contract resolver is used
string json3 = JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
Formatting = Formatting.Indented
});
优化内存使用
为了使应用程序始终保持快速,必须尽量减少.NET框架执行垃圾收集所花费的时间。在垃圾收集过程中,给太多对象分配地址或给非常大的对象分配地址可能会拖慢甚至停止应用程序。
为了最小化内存的使用、分配地址的对象的数目,Json.NET支持对流直接序列化和反序列化。当操作大于85KB的JSON文档时,每次读或写JSON一小片,而不是把整个JSON加载到内存中,是十分重要的,从而避免JSON字符串以large object heap告终。
反序列化字符串
HttpClient client = new HttpClient();
// read the json into a string
// string could potentially be very large and cause memory problems
string json = client.GetStringAsync("http://www.test.com/large.json").Result;
Person p = JsonConvert.DeserializeObject<Person>(json);
反序列化流
HttpClient client = new HttpClient();
using (Stream s = client.GetStreamAsync("http://www.test.com/large.json").Result)
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
JsonSerializer serializer = new JsonSerializer();
// read the json from a stream
// json size doesn't matter because only a small piece is read at a time from the HTTP request
Person p = serializer.Deserialize<Person>(reader);
}
JsonConverters
向SerializeObject或DeserializeObject传入JsonConverter提供了一个简单的方法,来完全改变如何序列化对象的方式。然而,存在一些开销,针对每一个值调用CanConvert方法,以检查是否需要由JsonConverter来处理处理序列化。
存在两种方法可以继续使用JsonConverter,而不需要任何开销。最简单的方法是利用JsonConverterAttribute指定JsonConverter。这个特性告诉序列化器,在序列化和反序列化此类型时始终使用转换器,而不需要检查。
使用JsonConverter配合JsonConverterAttribute
[JsonConverter(typeof(PersonConverter))]
public class Person
{
public Person()
{
Likes = new List<string>();
}
public string Name { get; set; }
public IList<string> Likes { get; private set; }
}
如果你想要转换到的类不是你自己的类,你也不能够使用一个特性,则依然可以使用JsonConverter,只要创建你自己的IContractResolver。
使用JsonConverter配合IContractResolver
public class ConverterContractResolver : DefaultContractResolver
{
public new static readonly ConverterContractResolver Instance = new ConverterContractResolver();
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
// this will only be called once and then cached
if (objectType == typeof(DateTime) || objectType == typeof(DateTimeOffset))
{
contract.Converter = new JavaScriptDateTimeConverter();
}
return contract;
}
}
在上面的示例中的IContractResolver将把所有的DateTime设置为使用JavaScriptDateConverter。
手工序列化
读取和写出JSON的绝对最快的方式是使用JsonTextReader/JsonTextWriter来直接手工序列化类型。使用reader或writer直接跳过来自序列化器的任何开销,譬如反射。
使用JsonTextWriter手工序列化
public static string ToJson(this Person p)
{
StringWriter sw = new StringWriter();
JsonTextWriter writer = new JsonTextWriter(sw);
// {
writer.WriteStartObject();
// "name" : "Jerry"
writer.WritePropertyName("name");
writer.WriteValue(p.Name);
// "likes": ["Comedy", "Superman"]
writer.WritePropertyName("likes");
writer.WriteStartArray();
foreach (string like in p.Likes)
{
writer.WriteValue(like);
}
writer.WriteEndArray();
// }
writer.WriteEndObject();
return sw.ToString();
}
如果性能很重要,你不介意多写代码来实现它,则这是你的最佳选择。你可以在此处进一步了解如何使用JsonReader/JsonWriter:JSON的读写基础