C# 一种高效的XML节点比较算法

C# 一种高效的XML节点比较算法,c#,xml,algorithm,comparison,performance,C#,Xml,Algorithm,Comparison,Performance,我想确定XML文档中两个不同的子节点是否相等。如果两个节点具有相同的属性集和子注释,并且所有子注释也相等(即整个子树应相等),则应将其视为相等 输入文档可能非常大(高达60MB,需要比较的节点超过100000个),性能是一个问题 检查两个节点是否相等的有效方法是什么 示例: <w:p> <w:pPr> <w:spacing w:after="120"/> </w:pPr> <w:r> <w:t>H

我想确定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#(.NET2.0);欢迎任何反馈和进一步的想法。也许有人已经有了一个好的解决方案

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

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

谢谢,
divo不是对您的问题的直接回答,而是与您想要实现的目标密切相关:看看(.net XML power tools)

这种方法怎么样:

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

// 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"'
按字母顺序执行,以说明不同的文档顺序

使用这些字符串作为键,使用对相应
节点的引用作为值,构建一个集合

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


我不能说它的性能有多好,但我想实现和发现它并不难。

我建议不要使用自己的散列创建函数,而是使用内置的
XNodeEqualityComparer
GetHashCode
方法。这保证了在创建结果时考虑属性和子代节点,也可以节省一些时间

您的代码如下所示:

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);
    }
}
XNodeEqualityComparer comparer=新的XNodeEqualityComparer();
XDocument doc=XDocument.Load(“XmlFile1.xml”);
字典nodeDictionary=新字典();
foreach(文档元素(“文档”).Elements(“节点”)中的XNode节点)
{
int hash=comparer.GetHashCode(节点);
if(nodeDictionary.ContainsKey(散列))
{
//已找到重复项。请在此处执行您的逻辑
// ...
}
其他的
{
添加(散列,节点);
}
}
我的XmlFile1.xml是:

<?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
最终将包含唯一的节点及其散列集合。使用
字典
ContainsKey
方法检测重复项,传入节点的散列,我们使用
XNodeEqualityComparer
GetHashCode
方法生成散列


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

即使正确定义

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

原因有很多:

  • XML文档是可能具有不同文本表示的树
  • 在比较中可以考虑也可以不考虑只有空格的节点
  • 在比较中可以考虑注释节点,也可以不考虑注释节点
  • PI节点可以在比较中考虑,也可以不考虑
  • 词汇差异:或
  • 不同的前缀可能与两个文档中的同一名称空间相关联
  • 命名空间节点可以显示为在doc1的节点上定义的,也可以显示为未定义但从doc2中相应节点的父节点继承的
  • doc1中的属性周围可以使用引号,但doc2中可以使用撇号
  • 实体可以在doc1中使用,但可以在doc2中预扩展
  • 这两个文档可能具有不同但语义相同的DTD
  • 等等
  • 因此,试图为两个XML文档的相等比较生成一个函数的正确实现似乎是幼稚和不现实的


    我的建议是将该函数与兼容的XPath 2.0引擎一起使用。

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

    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;
    }
    

    想法是使子节点的顺序变得重要,但属性的顺序并不重要。

    相关帖子:嗨,谢谢你的评论。XmlDiff似乎很好,但对于我的具体问题来说,它似乎相当沉重。我不需要找到任何关于差异的信息,一个简单的等于或不等于测试就足够了,我也不需要创建一个规范化的表示形式,该表示形式使用XmlDiff