有時候您必須讀取任意大的 XML 檔案並撰寫您的應用程式,讓應用程式的記憶體使用量可以預測。 如果您嘗試使用大型 XML 檔案填入 XML 樹狀結構,您的記憶體使用量將與檔案大小成正比,也就是,變成過度。 因此,您應該改用資料流技術。
其中一個選項是使用 XmlReader 撰寫您的應用程式。 然而,建議您使用 LINQ 查詢 XML 樹狀結構。 這麼做就可以撰寫自己的自訂座標軸方法。 如需詳細資訊,請參閱如何撰寫 LINQ to XML 座標軸方法。
若要撰寫您自己的座標軸方法,可以撰寫使用 XmlReader 讀取節點的小型方法,直到該方法到達您感興趣的其中一個節點。 然後,該方法會呼叫從 ReadFrom 讀取的 XmlReader,並具現化 XML 片段。 接著,其會針對列舉自訂座標軸方法的方法,透過 yield return 產生每個片段。 此時,您就可以在自訂座標軸方法上撰寫 LINQ 查詢。
在您僅需要處理一次來源文件的情況下,最適合使用資料流技術,而且您可以用文件的順序處理項目。 特定的標準查詢運算子 (例如,OrderBy) 會反覆查看其來源、收集所有資料、排序這些資料,最後產生順序中的第一個項目。 若您在產生第一個項目前使用具體化其來源的查詢運算子,則不會保留小量磁碟使用量。
範例:實作及使用從 URI 所指定檔案中串流 XML 片段的自訂座標軸方法
有時候問題會變得更有趣。 在下列 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 文件的排列方式相同。 這是簡化的實作。 較為複雜的實作方法則用於剖析無效的文件。
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using XmlReader reader = XmlReader.Create(uri);
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they're created.
// Loop through Customer elements
do
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Customer")
{
// Move to Name element
XElement? name = null;
do
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
{
name = XNode.ReadFrom(reader) as XElement;
break;
}
}
while (reader.Read());
// Loop through Item elements
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
{
if (XNode.ReadFrom(reader) is XElement item && name != null)
{
XElement tempRoot = new XElement("Root",
new XElement(name),
item
);
yield return item;
}
}
else if (!reader.Read())
break;
}
}
}
while (reader.Read());
}
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);
}
Imports System.Xml
Module Module1
Public Iterator Function StreamCustomerItem(uri As String) As IEnumerable(Of XElement)
Using reader As XmlReader = XmlReader.Create(uri)
reader.MoveToContent()
' Parse the file, save header information when encountered, And yield the
' Item XElement objects as they're created.
' Loop through Customer elements
Do
If reader.NodeType = XmlNodeType.Element And reader.Name = "Customer" Then
' Move to Name element
Dim name As XElement = Nothing
Do
If reader.NodeType = XmlNodeType.Element And reader.Name = "Name" Then
name = TryCast(XNode.ReadFrom(reader), XElement)
Exit Do
End If
Loop While reader.Read()
' Loop through Item elements
While reader.NodeType <> XmlNodeType.EndElement
If reader.NodeType = XmlNodeType.Element And reader.Name = "Item" Then
Dim item = TryCast(XNode.ReadFrom(reader), XElement)
If name IsNot Nothing AndAlso item IsNot Nothing Then
Dim tempRoot = <Root>
<Name><%= name.Value %></Name>
<%= item %>
</Root>
Yield item
End If
ElseIf Not reader.Read() Then
Exit While
End If
End While
End If
Loop While reader.Read()
End Using
End Function
Sub Main()
Dim xmlTree = <Root>
<%=
From el In 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
此程式碼會產生下列輸出:
<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>