序列化程序上的字符串实习生.反序列化()

本文关键字:反序列化 程序上 字符串 实习生 序列化 | 更新日期: 2023-09-27 18:31:19

我目前正在使用 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"
    }]
}]

添加了示例。 如您所见,组名值几乎在所有对象上都重复。 只是相关的代码发生了变化。这不是一个大问题,但随着数据集的增长,我宁愿不要过多地增加分配。

此外,似乎"代码"可能会重复,但它对每个人来说都是唯一的。 基本上是同一对象的多个标识符。

序列化程序上的字符串实习生.反序列化<T>()

如果您事先知道您的 4 个标准字符串,您可以将它们与 String.Intern() 一起实习(或者只是在某处将它们声明为字符串文字 - 完成这项工作),然后使用以下自定义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();
    }
}

但是,我强烈建议不要全局使用它,因为您最终可能会向内部字符串表添加大量字符串。 相反,将其应用于您确信包含少量唯一字符串重复项的特定字符串集合:

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 序列化程序的模式。 它们使用类调用"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 实际上会返回一个给定 char[]、开始和结束索引的现有字符串。 可以将 nameTable 从流中读取字符串的位置更进一步,从而绕过任何重复字符串的创建。但是,我不知道如何在 Json.Net 中做到这一点

作为其他答案(尤其是 https://stackoverflow.com/a/39605620/6713)中提供的序列化程序的替代方法,您可以编写自己的短暂寿命"interner"。这意味着您不会填满 CLR 的字符串表,一旦您的转换器超出范围(在反序列化完成后),则仅保留对字符串的引用将位于已反序列化的实体中。

public class ReusableStringConverter : JsonConverter<string>
{
    private readonly Dictionary<string, string> _items = new Dictionary<string, string>();
    public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var str = reader. Value as string;
        if (str == null)
            return null;
        if (str.Length == 0)
            return string.Empty;
        if (_items.TryGetValue(str, out var item))
        {
            return item;
        }
        else
        {
            _items[str] = str;
            return str;
        }
    }
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) => throw new NotImplementedException();
}

如果你的目标不是 netstandard2.0,你可以用 HashTable 替换 Dictionary(netstandard2.0 没有 TryGetValue)

对我们来说,非常粗略的基准是它将内存使用量从 2.4gb 减少到 1.4GB,并且只将处理时间从 61 秒增加到 63 秒