在.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/