代码之家  ›  专栏  ›  技术社区  ›  gustafc

XML序列化程序池字符串是否可以避免大型重复字符串?

  •  5
  • gustafc  · 技术社区  · 15 年前

    我有一些非常大的XML文件,我用 System.Xml.Serialization.XmlSerializer .它相当快(好吧,足够快),但我希望它能合并字符串,因为一些长字符串会出现很多次。

    XML看起来有点像这样:

    <Report>
        <Row>
            <Column name="A long column name!">hey</Column>
            <Column name="Another long column name!!">7</Column>
            <Column name="A third freaking long column name!!!">hax</Column>
            <Column name="Holy cow, can column names really be this long!?">true</Column>
        </Row>
        <Row>
            <Column name="A long column name!">yo</Column>
            <Column name="Another long column name!!">53</Column>
            <Column name="A third freaking long column name!!!">omg</Column>
            <Column name="Holy cow, can column names really be this long!?">true</Column>
        </Row>
        <!-- ... ~200k more rows go here... -->
    </Report>
    

    XML反序列化为以下类:

    class Report 
    {
        public Row[] Rows { get; set; }
    }
    class Row 
    {
        public Column[] Columns { get; set; }
    }
    class Column 
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }
    

    导入数据时,会为每个列名分配一个新字符串。我知道为什么会这样,但根据我的计算,这意味着一些重复的字符串占导入数据所用内存的大约50%。我认为花一些额外的CPU周期来将内存消耗减半是一个很好的折衷办法。有什么方法可以 XmlSerializer 池字符串,以便在下次发生gen0 gc时丢弃和回收重复项?


    另外,一些最后的注释:

    • 我无法更改XML架构。它是从第三方供应商导出的文件。

    • 我知道可以(理论上)使用 XmlReader 相反,它不仅允许我进行自己的字符串池,还允许我在导入过程中处理数据,以便在读取整个文件之前,并非所有的200K行都必须保存在RAM中。不过,我还是不想花时间编写和调试自定义解析器。实际的XML比示例要复杂一些,所以这是一项非常重要的任务。如上所述- XML串行化器 就我的目的而言,确实表现得很好,我只是想知道是否有一种简单的方法可以稍微调整一下。

    • 我可以自己写一个字符串池并在 Column.Name setter,但我不想(1)这意味着修改自动生成的代码,(2)它会打开与并发性和内存泄漏相关的一系列问题。

    • 不,通过“合用”,我的意思不是“实习”,因为那样会导致内存泄漏。

    4 回复  |  直到 15 年前
        1
  •  2
  •   Marc Gravell    15 年前

    就我个人而言,我毫不犹豫地手动启动实体——要么假设生成的代码的所有权,要么手动执行(并去掉数组;-p)。

    重新并发——您可能有一个线程静态池?嗯, XmlSerializer 只需使用一根线,这样就可以了。它还可以让你把游泳池扔掉。所以你可以吃点东西 喜欢 静态池,但每个线程。然后可能会调整设置器:

    class Column 
    {
        private string name, value;
        public string Name {
           get { return this.name; }
           set { this.name= MyPool.Get(value); }
        }
        public string Value{
           get { return this.value; }
           set { this.value = MyPool.Get(value); }
        }
    }
    

    哪里有静电 MyPool.Get 方法与静态字段对话( HashSet<string> ,大概)装饰 [ThreadStatic] .

        2
  •  1
  •   John Saunders    15 年前

    我建议你不要预先优化这个。等待它工作,分析结果,然后根据分析结果进行优化。您可能会发现还有一些其他的优化需要首先进行。

        3
  •  0
  •   Richard    15 年前

    你可以使用 OnDeserializedAttribute 若要定义在反序列化实例后调用的方法,请使用DataContract序列化程序(与WCF使用的一样),而不是使用XmlSerializer。

    或者,如果XML没有比示例复杂得多,那么为什么不通过XMLReader实现您自己的反序列化呢?

        4
  •  0
  •   itaiy    5 年前

    我知道它的旧线,但我找到了一个很好的方法:

    创造 XmlReader 它覆盖了 Value 属性的方式是,在返回值之前,检查字符串池中是否存在该属性,然后返回该属性。

    这个 价值 性质 XMLRead msdn :

    返回的值取决于节点的节点类型。以下 表列出了要返回值的节点类型。所有其他节点 类型返回string.empty。

    例如,对于 Attribute NodeType 它返回属性的值。

    因此,实现过程如下:

    public class StringPoolXmlTextReader : XmlTextReader
    {
        private readonly Dictionary<string, string> stringPool = new Dictionary<string, string>();
    
        internal StringPoolXmlTextReader(Stream stream)
            : base(stream)
        {
        }
    
        public override string Value
        {
            get
            {
                if (this.NodeType == XmlNodeType.Attribute)
                    return GetOrAddFromPool(base.Value);
    
                return base.Value;
            }
        }
    
        private string GetOrAddFromPool(string str)
        {
            if (str == null)
                return null;
    
            if (stringPool.TryGetValue(str, out var res) == false)
            {
                res = str;
                stringPool.Add(str, str);
            }
    
            return res;
        }
    }
    

    如何使用:

    using (var stream = File.Open(@"..\..\Report.xml", FileMode.Open))
    {
       var reader = new StringPoolXmlTextReader(stream);
       var ser = new XmlSerializer(typeof(Report));
       var data = (Report)ser.Deserialize(reader);
    }
    

    性能: 我用随机列值检查了200K行的性能,发现反序列化时间相同,并且 Report 内存从78551460字节减少到48890016字节(减少了约38%)。

    笔记:

    1. 示例继承自 XmlTextReader 但你可以继承任何 XMLRead
    2. 您还可以使用字符串池进行列值的重写值属性,如下所示 public override string Value => GetOrAddFromPool(base.Value); 但是,当值不重复时,它可以将反序列化时间增加大约20%(就像在我的测试中,它们是随机的)。