【问题标题】:Most efficient way to read the number of XML nodes directly under the root element直接读取根元素下的 XML 节点数的最有效方法
【发布时间】:2015-05-12 16:33:52
【问题描述】:

我需要计算 XML 流中根元素正下方的节点数。我不关心任何子节点。

例如,对于以下 XML,它应该返回 4:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <node1>
    <subnode1_1>
      <subnode_1_1_1>
        <subnode_1_1_1_1>…</subnode_1_1_1_1>
      </subnode_1_1_1>
      <subnode_1_1_2>…</subnode_1_1_2>
    </subnode1_1>
  </node1>
  <node2 />
  <node3>
    <subnode3_1>…</subnode3_1>
    <subnode3_2>…</subnode3_2>
    <subnode3_3>…</subnode3_3>
  </node3>
  <node4>…</node4>
</root>

在 C# 中执行此操作的最有效(我关心执行时间)方法是什么?假设我的 XML 正文为 Stream

【问题讨论】:

  • 您似乎只想计算元素节点。对吗?
  • 可以,但仅限根节点下的那些。
  • 为清楚起见,当你指的是元素时,你应该说“元素”。 “根节点”将是整个文档。一个 XML 1.1 文档至少有:xml 声明节点和根元素节点。

标签: c# xml performance stream


【解决方案1】:

您可以使用 Linq To XML 在一行中将其敲掉:

var count = XDocument.Load(stream).Root.Elements.Count();
//count = 4

就效率而言,在给出的两个答案之间,我的结果是:

var sw = Stopwatch.StartNew();

XmlDocument xml = new XmlDocument();
xml.Load(stream);
int i = xml.LastChild.ChildNodes.Count; 

sw.Stop();
//971 ticks

var sw = Stopwatch.StartNew();
var count = XDocument.Load(stream).Root.Elements().Count();
sw.Stop();
//860 ticks

实际上差异可以忽略不计,除非您进行多次迭代

【讨论】:

  • 为什么你认为它是最高效的?
  • @bpiec 我添加了一些数字
  • 对问题中的样本运行一些循环,与XmlDocument 相比,我让你的表现比你的数字建议的要好;大约好 50%,但仍然比我的方法慢一些。
【解决方案2】:

你不太可能比:

public static int GetImmediateChildrenCount(Stream stm)
{
  using(stm)
  {
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.CheckCharacters = false; //optomisation - best avoided.
    settings.DtdProcessing = DtdProcessing.Ignore;
    int count = 0;
    using(XmlReader rdr = XmlReader.Create(stm, settings))
      while(rdr.Read())
        if(rdr.NodeType == XmlNodeType.Element && rdr.Depth == 1)
          ++count;
    return count;
  }
}

实际上并没有编写专门的解析器来做到这一点。

上面扫描了XmlReader,忽略除了开始、结束和空元素标签的深度之外的所有内容,如果深度为1,则增加其计数;也就是根节点的正下方。

它肯定会比构造 XDocumentXmlDocument 的任何东西都快,因为它不会花费时间和内存,但如果你打算使用 XDocumentXmlDocument 做某事否则,这些方法会更快(计数位对它们来说很快,并且已经花费了构建对象所花费的时间)。

如果您要阅读几个这样的文档并且它们有很多共同的 xml 名称(元素和属性名称、名称空间名称和名称空间前缀),那么您最好保留 NameTable 对象的缓存你传入了settings.NameTable 属性。 NameTables 不是线程安全的,所以你不能只使用同一个,但是在“学习”新名称时它们是最昂贵的,并且重用它们会带来后续的性能提升。但这只有在每个文档中有很多相同的名称时才是正确的;如果文档非常不同,则它们不会从“先验知识”中受益,而您只是在浪费循环移动它们,而不是垃圾收集每个新 XmlReader 给您的默认文档。 (事实上​​,你让他们的查找速度稍微慢了一点)。

如果您真的想要绝对最高效,那么您可以通过阅读流并跟踪 &lt;...&gt;&lt;/...&gt;&lt;.../&gt; 来击败上述方法,但您也必须处理一堆特殊情况,因此您在上述方面的收获不太可能足以让您付出努力。

以您的示例进行 10000 次迭代的粗略数据:

XmlDocument:                     2387373
XDocument:                       1942206
XmlReader:                       1872387
XmlReader with reused NameTable: 1864708

根据您的示例,使用 136KiB 文件进行 100 次迭代的粗略数据:

XmlDocument:                     1887930
XDocument:                       1297059
XmlReader:                       996636
XmlReader with reused NameTable: 961763

【讨论】:

  • 有一个 Depth 属性 msdn.microsoft.com/en-us/library/… 您可以使用而不是用您的代码实现它。
  • @MartinHonnen 谢谢,我忘记了这一点,而且当它没有对这些元素做任何其他事情时的性能差异是不容忽视的。
  • 谢谢,你的回答真是天才!这是我从读取 150 MB 文件的 100 次迭代中得到的结果——您的解决方案:1131,XmlDocument:5812,XDocument:2898(以毫秒为单位的平均时间)。
  • 这里的XDocument 给我留下了深刻的印象,因为它非常方便,但肯定会击败它。您是否尝试过我的方法,无论是否重复使用NameTable?正如我所说,这样做是有帮助还是有阻碍取决于通过它推送的数据,因此最好用它编写代码,然后在有和没有比较的真实数据上进行测试运行;之后停止持有NameTable 比添加它要少得多。
  • 进一步的微选机会将比较 rdr.NodeType == XmlNodeType.Element &amp;&amp; rdr.Depth == 1rdr.Depth == 1 &amp;&amp; rdr.NodeType == XmlNodeType.Elementrdr.NodeType == XmlNodeType.Element &amp; rdr.Depth == 1rdr.Depth == 1 &amp; rdr.NodeType == XmlNodeType.Element。所有这些在逻辑上都是等价的,但对于前两个而言,捷径的可能性不同,并且可能避免后两个的分支错误预测。
【解决方案3】:

就这么简单:

XmlDocument xml = new XmlDocument();
xml.Load(/*path to your file*/);
int i = xml.LastChild.ChildNodes.Count; //as the xml header is first child
Console.WriteLine(i.ToString());

或者正如@Jonesy 所说:

int i = XDocument.Load(/*your stream*/).Root.Elements.Count();
Console.WriteLine(i.ToString());

两者都会输出4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 1970-01-01
    • 2018-04-13
    • 1970-01-01
    相关资源
    最近更新 更多