簡介
在.NET framework 中存在大量操作xml數據的類庫和api,但在.NET framework 3.5后我們的首選一般就是linq to xml。
linq to xml操作xml數據無論是XElement.Load方法還是XElement.Parse方法都會將整個xml文件加載到內存中,在xml文件超級大的情況下linq to xml就不太適合。
對於大型的xml文件最好的方法就是每次只讀取一部分,這樣逐漸的讀取整個xml文件,這個剛好對應XmlReader類。
XmlReader使用起來效率高,但操作沒有linq to xml方便,所以就希望取兩者之長:既有效率使用起來也如linq to xml一樣方便。
思路
XElement類有一個方法ReadFrom,此方法接受一個XmlReader參數 : XNode.ReadFrom 方法 (XmlReader)
在上面的鏈接MSDN上,其實已經有了對應的組合方式了,而且名字也不錯:執行大型 XML 文檔的流式轉換
static IEnumerable<XElement> StreamXElements(string uri, string matchname)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
using (XmlReader reader = XmlReader.Create(uri, settings))
{
reader.MoveToContent();
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name == matchname)
{
XElement el = XElement.ReadFrom(reader) as XElement;
if (el != null)
{
yield return el;
}
}
break;
}
}
}
}
以上代碼就是用XmlReader一直Read下去,然后碰到XmlNodeType.Element類型時就可以XElement.ReadFrom(reader)構建XElement,最重要的就是最后的yield return。
這樣目前為止,so far so good.
但在測試的時候,發現此方法有一個比較嚴重的bug,每次讀取一個XElement之后就會跳過一個XElement:

如以上的xml,在讀取第一個470002048節點之后,470002049節點就被跳過了。
這里其實就是XmlReader不小心Read too far的一個問題,read too far其實就是多read了一次,可以這樣理解:
initial read; (while "we're not at the end") { do stuff; read; }
再回到我們上面的代碼,其實在XElement.ReadFrom(reader)構建XElement之后,內部已經read了一次,但在while語句中我們還是在reader,這樣下一個XElement是不會讀到的。
那知道原因之后,解決起來也簡單了,這里就用reader.EOF 做判斷條件並去掉多余的一次read,具體代碼如下:
static IEnumerable<XElement> StreamXElements(string uri, string matchname)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
using (XmlReader reader = XmlReader.Create(uri, settings))
{
reader.MoveToContent();
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == matchname)
{
XElement el = XElement.ReadFrom(reader) as XElement;
if (el != null)
{
yield return el;
}
}
else
{
reader.Read();
}
}
}
}
總結
組合XmlReader和XElement的方式在MSDN中其實已經有了相應的文章介紹,但自己摸索的過程中還是有很多的收獲,參考文章如下:
http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
https://msdn.microsoft.com/en-us/library/mt693229.aspx
http://stackoverflow.com/questions/2441673/reading-xml-with-xmlreader-in-c-sharp
https://blogs.msdn.microsoft.com/xmlteam/2007/03/24/streaming-with-linq-to-xml-part-2/
