如何从 XmlReader 流式处理 XML 片段(LINQ to XML)
如果必须处理很大的 XML 文件,将整个 XML 树加载到内存可能不可行。 本文演示如何使用 C# 和 Visual Basic 中的 XmlReader 流式传输片段。
使用 XmlReader 读取 XElement 对象的一种最有效方式是编写您自己的自定义轴方法。 轴方法通常会返回一个集合,比如 IEnumerable<T> 的 XElement,如本文中的示例所示。 在自定义轴方法中,在通过调用 ReadFrom 方法创建 XML 片段后,可以使用 yield return
返回该集合。 这可为您的自定义轴方法提供延迟执行语义。
在从 XmlReader 对象创建 XML 树时,XmlReader 必须位于元素上。 ReadFrom 方法在读取该元素的结束标记之前不会返回。
如果想要创建一个部分树,可实例化 XmlReader,将读取器定位在要转换为 XElement 树的节点上,然后创建 XElement 对象。
文章 通过对标头信息的访问流式处理 XML 片段 包含有关如何流式处理更复杂的文档的信息。
文章 如何执行大型 XML 文档的流式转换 包含如何使用 LINQ to XML 在保持小内存需求量的同时转换极大 XML 文档的示例。
示例:创建一个自定义轴方法
本示例创建一个自定义轴方法。 可以通过使用 LINQ 查询来查询该方法。 自定义轴方法 StreamRootChildDoc
可以读取具有重复 Child
元素的文档。
using System.Xml;
using System.Xml.Linq;
static IEnumerable<XElement> StreamRootChildDoc(StringReader stringReader)
{
using XmlReader reader = XmlReader.Create(stringReader);
reader.MoveToContent();
// Parse the file and display each of the nodes.
while (true)
{
// If the current node is an element and named "Child"
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
{
// Get the current node and advance the reader to the next
if (XNode.ReadFrom(reader) is XElement el)
yield return el;
}
else if (!reader.Read())
break;
}
}
string markup = """
<Root>
<Child Key="01">
<GrandChild>aaa</GrandChild>
</Child>
<Child Key="02">
<GrandChild>bbb</GrandChild>
</Child>
<Child Key="03">
<GrandChild>ccc</GrandChild>
</Child>
</Root>
""";
IEnumerable<string> grandChildData =
from el in StreamRootChildDoc(new StringReader(markup))
where (int)el.Attribute("Key") > 1
select (string)el.Element("GrandChild");
foreach (string str in grandChildData)
Console.WriteLine(str);
Imports System.Xml
Module Module1
Public Iterator Function StreamRootChildDoc(stringReader As IO.StringReader) As IEnumerable(Of XElement)
Using reader As XmlReader = XmlReader.Create(stringReader)
reader.MoveToContent()
' Parse the file and display each of the nodes.
While True
' If the current node is an element and named "Child"
If reader.NodeType = XmlNodeType.Element And reader.Name = "Child" Then
' Get the current node and advance the reader to the next
Dim el As XElement = TryCast(XNode.ReadFrom(reader), XElement)
If (el IsNot Nothing) Then
Yield el
End If
ElseIf Not reader.Read() Then
Exit While
End If
End While
End Using
End Function
Sub Main()
Dim markup = "<Root>
<Child Key=""01"">
<GrandChild>aaa</GrandChild>
</Child>
<Child Key=""02"">
<GrandChild>bbb</GrandChild>
</Child>
<Child Key=""03"">
<GrandChild>ccc</GrandChild>
</Child>
</Root>"
Dim grandChildData =
From el In StreamRootChildDoc(New IO.StringReader(markup))
Where CInt(el.@Key) > 1
Select el.<GrandChild>.Value
For Each s In grandChildData
Console.WriteLine(s)
Next
End Sub
End Module
该示例产生下面的输出:
bbb
ccc
此示例中使用的技术即使对数百万个 Child
元素也保持较小的内存占用。