用于比较XML节点的高效算法

时间:2008-12-05 12:28:28

标签: c# xml algorithm comparison performance

我想确定XML文档中的两个不同的子节点是否相等。如果两个节点具有相同的属性集并且子注释和所有子注释相等(即整个子树应该相等),则应认为它们是相等的。

输入文档可能非常大(最多60MB,超过100000个节点需要比较),性能是个问题。

检查两个节点是否相等的有效方法是什么?

示例:

<w:p>
  <w:pPr>
    <w:spacing w:after="120"/>
  </w:pPr>
  <w:r>
    <w:t>Hello</w:t>
  </w:r>
</w:p>
<w:p>
  <w:pPr>
    <w:spacing w:after="240"/>
  </w:pPr>
  <w:r>
    <w:t>World</w:t>
  </w:r>
</w:p>

此XML片段描述了OpenXML文档中的段落。该算法将用于确定文档是否包含具有与文档前面的另一段相同属性(w:pPr节点)的段落(w:p节点)。

我有一个想法是将节点的外部XML存储在一个哈希集中(通常我必须首先得到一个规范的字符串表示,其中属性和子注释总是以相同的方式排序,但我可以期待我的节点已经处于这种形式)。

另一个想法是为每个节点创建一个XmlNode对象,并编写一个比较所有属性和子节点的比较器。

我的环境是C#(。Net 2.0);我们非常欢迎任何反馈和进一步的想法。也许有人甚至已经有了一个很好的解决方案?

编辑:微软的XmlDiff API实际上可以这样做,但我想知道是否会有更轻量级的方法。 XmlDiff似乎总是产生一个diffgram,并且总是先生成一个规范的节点表示,这两个都是我不需要的。

EDIT2:我终于根据这里提出的建议实现了我自己的XmlNodeEqualityComparer。非常感谢!!!!

谢谢, DIVO

5 个答案:

答案 0 :(得分:10)

我建议不要滚动你自己的哈希创建函数,而是依赖于内置的XNodeEqualityComparer的{​​{1}}方法。这可以保证在创建结果时考虑属性和后代节点,也可以节省一些时间。

您的代码如下所示:

GetHashCode

我的XmlFile1.xml是:

XNodeEqualityComparer comparer = new XNodeEqualityComparer();
XDocument doc = XDocument.Load("XmlFile1.xml");
Dictionary<int, XNode> nodeDictionary = new Dictionary<int, XNode>();

foreach (XNode node in doc.Elements("doc").Elements("node"))
{
    int hash = comparer.GetHashCode(node);
    if (nodeDictionary.ContainsKey(hash))
    {
        // A duplicate has been found. Execute your logic here
        // ...
    }
    else
    {
        nodeDictionary.Add(hash, node);
    }
}

<?xml version="1.0" encoding="utf-8" ?> <doc> <node att="A">Blah</node> <node att="A">Blah</node> <node att="B"> <inner>Innertext</inner> </node> <node>Blah</node> <node att="B"> <inner>Different</inner> </node> </doc> 最终将包含一组唯一的节点及其哈希值。使用nodeDictionary的{​​{1}}方法检测重复项,传入节点的哈希值,我们使用Dictionary的{​​{1}}方法生成该哈希值。

我认为这应该足够快,以满足您的需求。

答案 1 :(得分:3)

这种方法怎么样:

对于文档中的所有<w:pPr>个节点(我假设每个<w:p>不超过一个),将所有相关数据(元素名称,属性,值)连接成一个字符串:

// string format is really irrelevant, so this is just a bogus example
'!w:keep-with-next@value="true"!w:spacing@w:before="10"@w:after="120"'

按字母顺序执行此操作,以考虑不同的文档顺序。

使用这些字符串作为键以及对相应<w:p>节点的引用来构建集合作为值。

在执行此操作的过程中,当您发现集合中已存在给定键时,您会发现具有相同属性的段落。如果要继续收集,请使用节点列表作为集合值。

我不能说这会有多好,但我想这并不难实现并找出答案。

答案 2 :(得分:3)

即使正确定义

的问题,也是非常具有挑战性的

“当两个xml文档相同时?”

这有很多原因:

  1. XML文档是可能具有不同文本表示的树。
  2. 在比较中可能会考虑或不考虑仅限空格的节点
  3. 在比较中可能会考虑或不考虑评论节点
  4. 比较中可能会考虑或不考虑PI节点
  5. 词汇差异:或
  6. 不同的前缀可能与两个文档中的相同命名空间相关联
  7. 命名空间节点可以在doc1的节点上定义,并且未定义但是从doc2中相应节点的父节点继承而来。
  8. 可以在doc1中的属性周围使用引号,但可以在doc2
  9. 中使用撇号
  10. 实体可以在doc1中使用,但可以在doc2
  11. 中预先扩展
  12. 这两个文档可能有不同但语义相同的DTD
  13. 因此,尝试为两个XML文档的相等性比较生成函数的正确实现似乎是天真和不切实际的。

    我建议使用deep-equal()函数和兼容的XPath 2.0引擎。

答案 3 :(得分:2)

这是我试图解决部分问题的哈希函数。请注意,我在编写哈希函数方面的经验很少,并且主要是为了从人们那里得到关于它在解决这个特定问题方面的有效性的反馈。我不建议它在生产中使用。

static int HashXElement(XElement elem)
{
    int hash = 23;

    foreach (XAttribute attrib in elem.Attributes())
    {
        int attribHash = 23;
        attribHash = attribHash * 37 + attrib.Name.GetHashCode();
        attribHash = attribHash * 37 + attrib.Value.GetHashCode();
        hash = hash ^ attribHash;
    }

    foreach(XElement subElem in elem.Descendants())
    {
        hash = hash * 37 + XmlHash(subElem);
    }

    hash = hash * 37 + elem.Value.GetHashCode();

    return hash;
}

这些想法是为了使子节点的排序显着,但属性的排序并不重要。

答案 4 :(得分:0)

不是您问题的直接答案,而是与您尝试实现的内容密切相关:查看XmlDiff(.net XML电动工具)