HOW TO:具有標頭資訊存取權的資料流 XML 片段
有時候您必須讀取任意大的 XML 檔案並撰寫您的應用程式,讓應用程式的記憶體使用量可以預測。 如果您嘗試使用大型 XML 檔案填入 XML 樹狀結構,您的記憶體使用量將與檔案大小成正比,也就是,變成過度。 因此,您應該改用資料流技術。
其中一個選項是使用 XmlReader 撰寫您的應用程式。 但是,您可能想要使用 LINQ 查詢 XML 樹狀結構。 若發生這種情況,您可以撰寫自己的自訂座標軸方法。 如需詳細資訊,請參閱HOW TO:撰寫 LINQ to XML 軸方法。
若要撰寫您自己的座標軸方法,您可以撰寫使用 XmlReader 讀取節點的小方法,直到該方法到達您感興趣的其中一個節點。 然後,該方法會呼叫從 XmlReader 讀取的 ReadFrom,並具現化 XML 片段。 接著,它會針對列舉自訂座標軸方法的方法,透過 yield return 產生每個片段。 此時,您就可以在自訂座標軸方法上撰寫 LINQ 查詢。
在您僅需要處理一次來源文件的情況下,最適合使用資料流技術,而且您可以用文件的順序處理項目。 特定的標準查詢運算子 (例如,OrderBy) 會反覆查看其來源、收集所有資料、排序這些資料,最後產生順序中的第一個項目。 請注意,如果您在產生第一個項目前使用具體化其來源的查詢運算子,您將不會保留小的記憶體使用量。
範例
有時候問題會變得更有趣。 在下列 XML 文件中,您自訂座標軸方法的消費者也必須知道每個項目所屬客戶的名稱。
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
這個範例所採取的方法也是監看這個標頭資訊、儲存標頭資訊,然後建置同時包含您所列舉之標頭資訊與細節的小型 XML 樹狀結構。 這個座標軸方法接著就會產生這個新的小型 XML 樹狀結構。 該查詢將可以存取標頭資訊以及詳細資訊。
此方法擁有小的記憶體使用量。 產生每個細節 XML 片段時,不會針對上一個片段保留任何參考,而且這個片段適用於記憶體回收。 請注意,這個技術會在堆積上建立許多短期存在的物件。
下列範例顯示如何實作與使用從 URI 指定之檔案資料流 XML 片段的自訂座標軸方法。 這個自訂座標軸是特別撰寫的,讓它預備擁有 Customer、Name 和 Item 項目的文件,並預期這些項目將會與上述 Source.xml 文件的排列方式相同。 這是簡化的實作方法。 較為複雜的實作方法則用於剖析無效的文件。
注意事項 |
---|
下列範例使用 C# 的 yield return 建構函式。在 Visual Basic 中,對等程式碼是使用實作 IEnumerable(Of XElement) 介面的類別來提供。如需在 Visual Basic 中實作 IEnumerable(Of T) 的範例,請參閱逐步解說:在 Visual Basic 中實作 IEnumerable(Of T)。 |
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
// loop through Customer elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Customer")
{
// move to Name element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Name")
{
name = XElement.ReadFrom(reader) as XElement;
break;
}
}
// loop through Item elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Item")
{
item = XElement.ReadFrom(reader) as XElement;
if (item != null) {
XElement tempRoot = new XElement("Root",
new XElement(name)
);
tempRoot.Add(item);
yield return item;
}
}
}
}
}
}
}
static void Main(string[] args)
{
XElement xmlTree = new XElement("Root",
from el in StreamCustomerItem("Source.xml")
where (int)el.Element("Key") >= 3 && (int)el.Element("Key") <= 7
select new XElement("Item",
new XElement("Customer", (string)el.Parent.Element("Name")),
new XElement(el.Element("Key"))
)
);
Console.WriteLine(xmlTree);
}
Module Module1
Sub Main()
Dim xmlTree = <Root>
<%=
From el In New StreamCustomerItem("Source.xml")
Let itemKey = CInt(el.<Key>.Value)
Where itemKey >= 3 AndAlso itemKey <= 7
Select <Item>
<Customer><%= el.Parent.<Name>.Value %></Customer>
<%= el.<Key> %>
</Item>
%>
</Root>
Console.WriteLine(xmlTree)
End Sub
End Module
Public Class StreamCustomerItem
Implements IEnumerable(Of XElement)
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
End Sub
Public Function GetEnumerator() As IEnumerator(Of XElement) Implements IEnumerable(Of XElement).GetEnumerator
Return New StreamCustomerItemEnumerator(_uri)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
End Class
Public Class StreamCustomerItemEnumerator
Implements IEnumerator(Of XElement)
Private _current As XElement
Private _customerName As String
Private _reader As Xml.XmlReader
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
_reader = Xml.XmlReader.Create(_uri)
_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
Dim item As XElement
Dim name As XElement
' Parse the file, save header information when encountered, and return the
' current Item XElement.
' loop through Customer elements
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element Then
Select Case _reader.Name
Case "Customer"
' move to Name element
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element AndAlso
_reader.Name = "Name" Then
name = TryCast(XElement.ReadFrom(_reader), XElement)
_customerName = If(name IsNot Nothing, name.Value, "")
Exit While
End If
End While
Case "Item"
item = TryCast(XElement.ReadFrom(_reader), XElement)
Dim tempRoot = <Root>
<Name><%= _customerName %></Name>
<%= item %>
</Root>
_current = item
Return True
End Select
End If
End While
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
_reader = Xml.XmlReader.Create(_uri)
_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
此程式碼會產生下列輸出:
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
</Root>