【问题标题】:Is there a way to speed up this code that finds data changes in two XML files?有没有办法加快在两个 XML 文件中查找数据更改的代码?
【发布时间】:2010-01-08 13:32:13
【问题描述】:

以下代码比较两个 XML 文本并返回它们之间的数据变化的集合。

此代码运行良好,但需要尽可能资源友好

在 LINQ 中是否有更快的方法来执行此操作,例如不创建两个 XElement 集合并比较它们的每个字段的差异?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace TestXmlDiff8822
{
    class Program
    {
        static void Main(string[] args)
        {
            XDocument xdoc1 = XDocument.Parse(GetXml1());
            XDocument xdoc2 = XDocument.Parse(GetXml2());

            List<HistoryFieldChange> hfcList = GetHistoryFieldChanges(xdoc1, xdoc2);

            foreach (var hfc in hfcList)
            {
                Console.WriteLine("{0}: from {1} to {2}", hfc.FieldName, hfc.ValueBefore, hfc.ValueAfter);  
            }

            Console.ReadLine();
        }

        static public List<HistoryFieldChange> GetHistoryFieldChanges(XDocument xdoc1, XDocument xdoc2)
        {
            List<HistoryFieldChange> hfcList = new List<HistoryFieldChange>();

            var elements1 = from e in xdoc1.Root.Elements()
                           select e;

            var elements2 = from e in xdoc2.Root.Elements()
                           select e;

            for (int i = 0; i < elements1.Count(); i++)
            {
                XElement element1 = elements1.ElementAt(i);
                XElement element2 = elements2.ElementAt(i);

                if (element1.Value != element2.Value)
                {
                    HistoryFieldChange hfc = new HistoryFieldChange();
                    hfc.EntityName = xdoc1.Root.Name.ToString();
                    hfc.FieldName = element1.Name.ToString();
                    hfc.KindOfChange = "fieldDataChange";
                    hfc.ObjectReference = (xdoc1.Descendants("Id").FirstOrDefault()).Value;
                    hfc.ValueBefore = element1.Value;
                    hfc.ValueAfter = element2.Value;
                    hfcList.Add(hfc);
                }
            }

            return hfcList;
        }

        public static string GetXml1()
        {
            return @"
<Customer>
    <Id>111</Id>
    <FirstName>Sue</FirstName>
    <LastName>Smith</LastName>
</Customer>
";
        }



        public static string GetXml2()
        {
            return @"
<Customer>
    <Id>111</Id>
    <FirstName>Sue2</FirstName>
    <LastName>Smith-Thompson</LastName>
</Customer>
";
        }
    }

    public class HistoryFieldChange
    {
        public string EntityName { get; set; }
        public string FieldName { get; set; }
        public string ObjectReference { get; set; }
        public string KindOfChange { get; set; }
        public string ValueBefore { get; set; }
        public string ValueAfter { get; set; }
    }
}

【问题讨论】:

  • 这两个文件根本没有改变(相同)是否很常见?或者那是一种罕见的情况?
  • 理论上总会有一些变化,因为它们是更新对象的序列化,这是一种报告字段随时间变化的方法

标签: c# xml linq performance


【解决方案1】:

您应该能够使用元素名称上的 linq 连接来获取具有不同值的所有元素。

var name = xdoc1.Root.Name.ToString();
var id = (xdoc1.Descendants("Id").FirstOrDefault()).Value;

var diff =  from o in xdoc1.Root.Elements()
        join n in xdoc2.Root.Elements() on o.Name equals n.Name
        where o.Value != n.Value
        select new HistoryFieldChange() {
                EntityName = name,
                FieldName = o.Name.ToString(),
                KindOfChange = "fieldDataChange",
                ObjectReference = id,
                ValueBefore = o.Value,
                ValueAfter = n.Value,
        };

这种方法的一个优点是它很容易为多核机器并行化,只需使用 PLinq 和 AsParallel 扩展方法。

var diff =  from o in xdoc1.Root.Elements()
        join n in xdoc2.Root.Elements().AsParallel() on o.Name equals n.Name
        where o.Value != n.Value
        ...

瞧,如果查询可以在您的计算机上并行化,那么 PLinq 将自动处理它。这将加快大型文档的速度,但如果您的文档很小,您可以通过并行化调用 GetHistoryFieldChanges 的外部循环使用类似 Parallel.For 来获得更好的加速。

另一个好处是你可以简单地从GetHistoryFieldChanges返回IEnumerable,不需要浪费时间分配一个List,项目将在枚举时返回,直到那时才执行Linq查询。

IEnumerable<HistoryFieldChange> GetHistoryFieldChanges(...)

这里是原始、Yannick 的有序和我的非并行 Linq-only 实现的 1M 次迭代的时间。在我的 2.8ghz 笔记本电脑上运行 this code

Elapsed Orig    3262ms
All Linq        1761ms
In Order Only   2383ms

我注意到一件有趣的事情......在调试模式下运行代码,然后在发布模式下运行,编译器可以优化纯 Linq 版本的程度令人惊讶。我认为返回 IEnumerable 对编译器有很大帮助。

【讨论】:

    【解决方案2】:

    编辑:

    我必须承认,我没想到 LINQ 实现会比使用有序数据的幼稚迭代方法更快。但这表明性能瓶颈并不总是在明显的地方。

    正如我所说的,我假设您按顺序进行比较是有原因的,所以也许这个实现仍然可以使用。当然,它不像 joshperry 的实现那样清晰。但就性能而言,它应该夺冠。

    static public IEnumerable<HistoryFieldChange> GetHistoryFieldChanges2(XDocument xdoc1, XDocument xdoc2)
    {
      string id = xdoc1.Descendants("Id").FirstOrDefault().Value;
      string name = xdoc1.Root.Name.ToString();
    
      IEnumerator<XElement> enumerator1 = xdoc1.Root.Elements().GetEnumerator();
      IEnumerator<XElement> enumerator2 = xdoc2.Root.Elements().GetEnumerator();
    
      for (; enumerator1.MoveNext() && enumerator2.MoveNext(); )
      {
        XElement element1 = enumerator1.Current;
        XElement element2 = enumerator2.Current;
    
        if (element1.Value != element2.Value)
        {
          HistoryFieldChange hfc = new HistoryFieldChange();
          hfc.EntityName = name;
          hfc.FieldName = element1.Name.ToString();
          hfc.KindOfChange = "fieldDataChange";
          hfc.ObjectReference = id;
          hfc.ValueBefore = element1.Value;
          hfc.ValueAfter = element2.Value;
          yield return hfc;
        }
      }
    }
    

    【讨论】:

    • 请随意给我投反对票,但请告诉我原因,以便我更改回复。
    • “在 LINQ 中是否有更快的方法来执行此操作......”来自问题。这根本不符合 OP 的标准,恕我直言,有序约束严重阻碍了该技术的实用性。您还声称 Linq 带来了不必要的开销,显然情况并非如此。
    • 我不会将标签的顺序视为重要的“幼稚”。虽然 XML 定义属性的顺序无关紧要,但它没有为标签做出这样的定义。因此,必须假定标签的顺序对用户很重要。
    • @joshperry:您对 LINQ 部分的看法是完全正确的,但是我的建议是反对使用 LINQ。有序约束确实限制了有用性,但我们无法知道确切的约束是什么。考虑到他的初始实现按顺序处理节点,有理由假设输入数据非常一致。
    • @Craig Stuntz,就像你说的,XML 规范没有说明元素的顺序;它只声明约束到 DTD 列表的元素必须按顺序显示。给定一个 DTD 或 Schema(除非列表定义),一组定义的元素将按任何顺序进行验证。所以元素之所以有顺序只是因为文档中对顺序的词汇要求,我认为我们不能假设任何事情。
    【解决方案3】:

    您是否考虑过使用XmlDiff

    【讨论】:

    • 是的,我也在尝试,如果可能的话,我想要一个不包括在正在运行的应用程序中安装任何东西的纯代码示例。
    • 顺便说一句,XmlDiff 中有一个错误会导致空白但非空属性的索引超出范围异常。 (例如 padding=" ")几个月前我在 cowboycoder.wordpress.com 上发布了修复代码。
    猜你喜欢
    • 2011-02-09
    • 1970-01-01
    • 2012-02-17
    • 1970-01-01
    • 2022-11-03
    • 2015-10-08
    • 2020-12-09
    • 2019-10-17
    • 1970-01-01
    相关资源
    最近更新 更多