zoukankan      html  css  js  c++  java
  • 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 文档的流式转换

    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/

  • 相关阅读:
    关于”数据库连接串中的 |DataDirectory|”
    【入门经典】Master和Content页面之一
    使用数据绑定控件
    ASP.NET数据绑定概述
    【入门经典】多层次的母版页(嵌套)
    【入门经典】在母版页中使用CSS
    内联表达式
    使用【表格式】数据绑定控件
    SQL Server 2005数据库从【Express版】迁移到【正式版】连接字符串存在的问题
    【入门经典】准备工作
  • 原文地址:https://www.cnblogs.com/julyluo/p/5505792.html
Copyright © 2011-2022 走看看