【问题标题】:Slow SelectSingleNode慢选择单节点
【发布时间】:2010-09-27 23:14:55
【问题描述】:

我有一个简单的结构化 XML 文件,如下所示:

<ttest ID="ttest00001", NickName="map00001"/>
<ttest ID="ttest00002", NickName="map00002"/>
<ttest ID="ttest00003", NickName="map00003"/>
<ttest ID="ttest00004", NickName="map00004"/>

..... 此 xml 文件大小约为 2.5MB。

在我的源代码中,我将有一个循环来获取昵称

在每个循环中,我都有这样的东西:

nickNameLoopNum = MyXmlDoc.SelectSingleNode("//ttest[@ID=' + testloopNum + "']").Attributes["NickName"].Value

这一行将花费我 30 到 40 毫秒。

我搜索了一些旧文章(可以追溯到 2002 年)说,使用某种编译的“xpath”可以帮助解决这种情况,但那是 5 年前的事了。我想知道有没有一种现代的做法可以让它更快? (我使用的是 .NET 3.5)

【问题讨论】:

  • 在提供解决方案之前 - 循环的目标是什么?您是否只想获取所有昵称的列表? ID="ttest*" 的节点的所有昵称?与其浪费每个 SelectSingleNode 的 30/40 毫秒,我们可以在一次调用中完成所有这些 - 但我不知道您的目标是什么。

标签: .net xml performance .net-3.5


【解决方案1】:

在 XPath 表达式中使用“//”缩写会导致效率大大降低,因为它会导致搜索整个 XML 文档。反复使用 '//' 会增加这种低效率。

一种有效的解决方案是通过仅评估一个 XPath 表达式来获取所有“NickName”属性节点:

   ttest/@NickName

上下文节点是所有“ttest”元素的父节点。

C# 代码如下所示:

    int n = 15;
    XmlDocument doc = new XmlDocument();
    doc.Load("MyFile.xml");

    XmlNodeList nodeList;
    XmlNode top = doc.DocumentElement;
    nodeList =
        top.SelectNodes("ttest/@NickName");

    // Get the N-th NickName, can be done in a loop for
    // all n in a range

    string nickName = nodeList[n].Value;

这里我们假设“ttest”元素是 xml 文档顶部元素的子元素。

总而言之,提出了一种有效的解决方案,它只对 XPath 表达式求值一次,并将所有结果放在一个方便的 IEnumerable 对象(可用作数组)中,以访问O(c)时间。

【讨论】:

  • 检查我的答案并自己测试代码。你会感到惊讶。
  • @argatha:我没有说过立即选择属性更快,只是选择必要的节点比扫描整个树更快。如果整个树只包含我们想要选择的节点,那么我们可以选择任何一种方式。但是,在实践中有很多情况,XML 文档包含许多根本不包含我们的任何节点的大子树。测试这些案例并保持公平。
【解决方案2】:

您已经在使用 XPath(“//ttest...”),这是访问文档节点最慢的方式,因为“//”语法在整个文档中查找。

尝试类似...

foreach (XMLNode node in MyXmlDoc.ChildNodes) {
    ...
}

相反,不需要 xpath,它应该更快。 (隐含假设它是一个没有嵌套的“平面”xml 文件。如果是这样,你很快就会递归我的小伙子)。

【讨论】:

    【解决方案3】:

    回答 Dimitre

    实际上...选择整个节点比只选择属性要快。

    我有一个单元测试对下面的代码进行基准测试,并且(惊人地)选择完整节点并处理属性比选择属性并立即获取值更快。

    将其放入 10000 次迭代循环并交换 cmets 以测试每种方式。

     //XmlNodeList nodeList = document.SelectNodes("test/@NickName");
                XmlNodeList nodeList = document.SelectNodes("test");
                foreach (XmlNode node in nodeList)
                {
                    //string nickName = node.Value;
                    string nickName = ((XmlAttribute)node.Attributes.GetNamedItem("NickName")).Value;
    
                }

    我知道这违反直觉,但是....您必须衡量!

    【讨论】:

      【解决方案4】:

      在这种情况下,您可能需要考虑将 XML 文件中的昵称预先读取到一个数组(如果您的测试 ID 真的只是顺序整数)或字典(如果不是)中,然后使用它来定位每个昵称,而不是尝试做一堆 XPath 查询。通过这种方式,您可能会在查找时获得更好的性能。

      编辑:像这样的东西(伪代码)

      var nicknames = new Dictionary<string, string>();
      
      foreach (XmlNode node in MyXmlDoc.ChildNodes)
      {
          if (node is XmlElement)
          {
              nicknames.Add(node.Attributes["ID"], node.Attributes["NickName"]);
          }
      }
      
      ...
      
      nickNameLoopNum = nicknames[testLoopNum];
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-10
        • 2015-10-22
        相关资源
        最近更新 更多