XmlReader和XElement组合之读取大型xml文档
简介
在.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 文档的流式转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异