如何從 XmlReader 串流 XML 片段 (LINQ to XML)
當您必須處理大型 XML 檔案時,可能無法將整個 XML 樹狀載入記憶體中。 本文說明如何使用 C# 和 Visual Basic 中的 XmlReader 串流片段。
使用 XmlReader 讀取 XElement 物件的其中一個最有效方式為,撰寫您自己自訂的座標軸方法。 座標軸方法通常會傳回集合,例如,IEnumerable<T> 的 XElement,如本文中的範例所示。 在自訂座標軸方法中,呼叫 ReadFrom 方法來建立 XML 片段後,使用 yield return
傳回集合。 這會將延後執行語意 (Semantics) 提供給您自訂的座標軸方法。
當您從 XmlReader 物件建立 XML 樹狀結構時,XmlReader 必須定位在項目上。 ReadFrom 方法在讀取項目的關閉標記前不會傳回。
如果您要建立部分樹狀結構,您可以具現化 XmlReader、將讀取器定位在您要轉換為 XElement 樹狀結構的節點上,然後建立 XElement 物件。
如何串流 XML 片段並存取標頭資訊一文包含如何串流更複雜文件的資訊。
如何執行大型 XML 文件的串流轉換一文包含使用 LINQ to XML 轉換非常大的 XML 文件,同時維護少量記憶體使用量的範例。
範例:建立自訂座標軸方法
這個範例會建立自訂座標軸方法。 您可以使用 LINQ 查詢進行查詢。 自訂座標軸方法 StreamRootChildDoc
可讀取具有重複 Child
元素的文件。
static IEnumerable<XElement> StreamRootChildDoc(StringReader stringReader)
{
using (XmlReader reader = XmlReader.Create(stringReader))
{
reader.MoveToContent();
// Parse the file and display each of the nodes.
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name == "Child") {
XElement el = XElement.ReadFrom(reader) as XElement;
if (el != null)
yield return el;
}
break;
}
}
}
}
static void Main(string[] args)
{
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);
}
}
Module Module1
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 New 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
Public Class StreamRootChildDoc
Implements IEnumerable(Of XElement)
Private _stringReader As IO.StringReader
Public Sub New(ByVal stringReader As IO.StringReader)
_stringReader = stringReader
End Sub
Public Function GetEnumerator() As IEnumerator(Of XElement) Implements IEnumerable(Of XElement).GetEnumerator
Return New StreamChildEnumerator(_stringReader)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
End Class
Public Class StreamChildEnumerator
Implements IEnumerator(Of XElement)
Private _current As XElement
Private _reader As Xml.XmlReader
Private _stringReader As IO.StringReader
Public Sub New(ByVal stringReader As IO.StringReader)
_stringReader = stringReader
_reader = Xml.XmlReader.Create(_stringReader)
_reader.MoveToContent()
End Sub
Public ReadOnly Property Current As XElement Implements IEnumerator(Of XElement).Current
Get
Return _current
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Me.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
While _reader.Read()
Select Case _reader.NodeType
Case Xml.XmlNodeType.Element
Dim el = TryCast(XElement.ReadFrom(_reader), XElement)
If el IsNot Nothing Then
_current = el
Return True
End If
End Select
End While
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
_reader = Xml.XmlReader.Create(_stringReader)
_reader.MoveToContent()
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
_reader.Close()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
這個範例會產生下列輸出:
bbb
ccc
對於數百萬個 Child
元素,此範例中使用的技術即使仍維持少量的記憶體使用量。