serializer.Deserialize<T>() 上的字符串实习生
string Intern on serializer.Deserialize<T>()
我目前正在使用 json.net 反序列化一个字符串,它是中等大小的对象集合。总共约 7000 项。
每个项目都有一个由 4 个相同字符串组成的循环组,在内存分析中,这会创建大约 40,000 个引用,具体取决于嵌套等。
有没有办法让序列化程序对每个相同的字符串使用相同的引用?
示例Json:
[{
"name":"jon bones",
"groups":[{
"groupName":"Region",
"code":"1"
},{
"groupName":"Class",
"code":"4"
}]
},
{
"name":"Swan moans",
"groups":[{
"groupName":"Region",
"code":"12"
},{
"groupName":"Class",
"code":"1"
}]
}]
添加了示例。如您所见,groupName 值在几乎所有对象上重复出现。只是相关代码发生了变化。这不是一个大问题,但随着数据集的增长,我宁愿不要增加太多分配。
另外,"code" 似乎可能会重复,但对每个人来说都是独一无二的。基本上是同一对象的多个标识符。
如果您事先知道您的 4 个标准字符串,您可以使用 String.Intern()
(or just declare them as string literals somewhere -- that does the job) then use the following custom JsonConverter
对它们进行实习,以将所有 JSON 字符串文字转换为它们的实习值(如果找到的话):
public class InternedStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
return String.IsInterned(s) ?? s;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
这可以通过序列化程序设置全局应用:
var settings = new JsonSerializerSettings { Converters = new [] { new InternedStringConverter() } };
var root = JsonConvert.DeserializeObject<RootObject>(jsonString, settings);
您也可以使用 JsonPropertyAttribute.ItemConverterType
:
将其应用于特定的字符串集合
public class Group
{
[JsonProperty(ItemConverterType = typeof(InternedStringConverter))]
public List<string> StandardStrings { get; set; }
}
如果您事先不知道这 4 个字符串,您可以创建一个转换器,在读取字符串时对其进行实习:
public class AutoInterningStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when a converter is applied directly to a property.
throw new NotImplementedException("AutoInterningStringConverter should not be used globally");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
return String.Intern(s);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
但是,我强烈建议不要在全局范围内使用它,因为您最终可能会向内部字符串 table 添加大量字符串。相反,仅将其应用于您确信包含少量唯一字符串的重复项的特定字符串集合:
public class Group
{
[JsonProperty(ItemConverterType = typeof(AutoInterningStringConverter))]
public List<string> StandardStrings { get; set; }
}
更新
根据您更新的问题,我看到您有具有标准值的字符串属性,而不是具有标准值的字符串集合。因此,您将对每个使用 [JsonConverter(typeof(AutoInterningStringConverter))]
:
public class Group
{
[JsonConverter(typeof(AutoInterningStringConverter))]
public string groupName { get; set; }
public string code { get; set; }
}
正如其他答案中指出的那样,由于该分配的生命周期,您在使用 String.Intern 时需要非常小心。对于一小组经常使用的字符串,这可能是合适的。
对于我们的方案,我选择遵循 .Net 中 XML 序列化程序的模式。他们使用 class 调用 "System.Xml.NameTable" 来解析 XML 文档中唯一出现的字符串。我遵循了上面 'dbc' 提供的实现模式,但使用了 NameTable 而不是 String.Intern
public class JsonNameTable
: System.Xml.NameTable
{
}
public class JsonNameTableConverter
: JsonConverter
{
private JsonNameTable _nameTable;
public JsonNameTableConverter(JsonNameTable nameTable)
{
_nameTable = nameTable;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)Newtonsoft.Json.Linq.JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
if (s != null)
{
s = _nameTable.Add(s);
}
return s;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
然后在使用代码中,设置一个转换器到Json设置
JsonNameTable nameTable = new JsonNameTable();
settings.Converters.Add(new JsonNameTableConverter(nameTable));
这允许您共享字符串,并通过引用 JsonNameTable 来控制字符串的生命周期。
此处可能可以进行改进:NameTable 实际上会 return 给定 char[]、开始和结束索引的现有字符串。有可能将 nameTable 进一步降低一个级别,从流中读取字符串,从而绕过甚至任何重复字符串的创建。但是,我不知道如何在 Json.Net
中做到这一点
我目前正在使用 json.net 反序列化一个字符串,它是中等大小的对象集合。总共约 7000 项。
每个项目都有一个由 4 个相同字符串组成的循环组,在内存分析中,这会创建大约 40,000 个引用,具体取决于嵌套等。
有没有办法让序列化程序对每个相同的字符串使用相同的引用?
示例Json:
[{
"name":"jon bones",
"groups":[{
"groupName":"Region",
"code":"1"
},{
"groupName":"Class",
"code":"4"
}]
},
{
"name":"Swan moans",
"groups":[{
"groupName":"Region",
"code":"12"
},{
"groupName":"Class",
"code":"1"
}]
}]
添加了示例。如您所见,groupName 值在几乎所有对象上重复出现。只是相关代码发生了变化。这不是一个大问题,但随着数据集的增长,我宁愿不要增加太多分配。
另外,"code" 似乎可能会重复,但对每个人来说都是独一无二的。基本上是同一对象的多个标识符。
如果您事先知道您的 4 个标准字符串,您可以使用 String.Intern()
(or just declare them as string literals somewhere -- that does the job) then use the following custom JsonConverter
对它们进行实习,以将所有 JSON 字符串文字转换为它们的实习值(如果找到的话):
public class InternedStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
return String.IsInterned(s) ?? s;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
这可以通过序列化程序设置全局应用:
var settings = new JsonSerializerSettings { Converters = new [] { new InternedStringConverter() } };
var root = JsonConvert.DeserializeObject<RootObject>(jsonString, settings);
您也可以使用 JsonPropertyAttribute.ItemConverterType
:
public class Group
{
[JsonProperty(ItemConverterType = typeof(InternedStringConverter))]
public List<string> StandardStrings { get; set; }
}
如果您事先不知道这 4 个字符串,您可以创建一个转换器,在读取字符串时对其进行实习:
public class AutoInterningStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when a converter is applied directly to a property.
throw new NotImplementedException("AutoInterningStringConverter should not be used globally");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
return String.Intern(s);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
但是,我强烈建议不要在全局范围内使用它,因为您最终可能会向内部字符串 table 添加大量字符串。相反,仅将其应用于您确信包含少量唯一字符串的重复项的特定字符串集合:
public class Group
{
[JsonProperty(ItemConverterType = typeof(AutoInterningStringConverter))]
public List<string> StandardStrings { get; set; }
}
更新
根据您更新的问题,我看到您有具有标准值的字符串属性,而不是具有标准值的字符串集合。因此,您将对每个使用 [JsonConverter(typeof(AutoInterningStringConverter))]
:
public class Group
{
[JsonConverter(typeof(AutoInterningStringConverter))]
public string groupName { get; set; }
public string code { get; set; }
}
正如其他答案中指出的那样,由于该分配的生命周期,您在使用 String.Intern 时需要非常小心。对于一小组经常使用的字符串,这可能是合适的。
对于我们的方案,我选择遵循 .Net 中 XML 序列化程序的模式。他们使用 class 调用 "System.Xml.NameTable" 来解析 XML 文档中唯一出现的字符串。我遵循了上面 'dbc' 提供的实现模式,但使用了 NameTable 而不是 String.Intern
public class JsonNameTable
: System.Xml.NameTable
{
}
public class JsonNameTableConverter
: JsonConverter
{
private JsonNameTable _nameTable;
public JsonNameTableConverter(JsonNameTable nameTable)
{
_nameTable = nameTable;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)Newtonsoft.Json.Linq.JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
if (s != null)
{
s = _nameTable.Add(s);
}
return s;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
然后在使用代码中,设置一个转换器到Json设置
JsonNameTable nameTable = new JsonNameTable();
settings.Converters.Add(new JsonNameTableConverter(nameTable));
这允许您共享字符串,并通过引用 JsonNameTable 来控制字符串的生命周期。
此处可能可以进行改进:NameTable 实际上会 return 给定 char[]、开始和结束索引的现有字符串。有可能将 nameTable 进一步降低一个级别,从流中读取字符串,从而绕过甚至任何重复字符串的创建。但是,我不知道如何在 Json.Net
中做到这一点