C#操作XML(六)

一、前言

  上集介绍了使用XmlWriter如何写一个大型的xml,不难发现XmlWriter使用起来略比Linq to Xml麻烦一些,不过优势却是基本不消耗内存。不过XmlWriter的功能仅仅是写Xml,要读取Xml则需要依靠XmlReader,这就是今天的主角。、

二、准备工作

  首先,准备一个大型的Xml:

private static void CreateLargeXmlFile(string fileName)
{
    using (var writer = XmlWriter.Create(fileName, new XmlWriterSettings { Indent = true }))
    {
        writer.WriteStartDocument();
        writer.WriteStartElement("root");
        for (int i = 0; i < 100; i++)
        {
            writer.WriteStartElement("folder");
            writer.WriteAttributeString("name", i.ToString());
            for (int j = 0; j < 100; j++)
            {
                writer.WriteStartElement("folder");
                writer.WriteAttributeString("name", j.ToString());
                for (int k = 0; k < 1000; k++)
                {
                    writer.WriteStartElement("file");
                    writer.WriteAttributeString("name", k.ToString());
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }
        writer.WriteFullEndElement();
        writer.WriteEndDocument();
    }
}

  执行这个方法,就可以获得一个250M的大型Xml。

三、使用XmlReader读取Xml

1. 基础

using (var reader = XmlReader.Create("test.xml"))
{
    while (reader.ReadToFollowing("file"))
    {
        var name = reader.GetAttribute("name");
        if (name.EndsWith("0"))
        {
            Console.WriteLine(name);
        }
    }
}

  这段代码将打印出Xml中所有file节点,并且name是以"0"位结尾的name属性。

2. 升级版

  XmlReader还有不少的方法,不过这样使用,实在是太累了。对于大多数的操作而言,Linq to Xml的API已经足够强大,所以不妨依葫芦画瓢,来一个简易版的Linq to XmlReader。

  a. 扩展方法  

public static class ElementsExtension
{
    public static IEnumerable<XmlReader> Elements(this XmlReader reader, string name)
    {
        reader.Read();
        while (reader.ReadToNextSibling(name))
        {
            var result = reader.ReadSubtree();
            result.Read();
            yield return result;
            result.Close();
        }
    }
}

  使用扩展方法:

private static void ReadByReader()
{
    using (var reader = XmlReader.Create("test.xml"))
    {
        foreach (var item in from x in reader.Elements("root").First().Elements("folder")
                                let parentName = x.GetAttribute("name")
                                where int.Parse(parentName) % 20 == 0
                                from y in x.Elements("folder")
                                let folderName = y.GetAttribute("name")
                                where int.Parse(folderName) % 30 == 0
                                select string.Format("folder:{0}\tfolder:{1}", parentName, folderName))
        {
            Console.WriteLine(item);
        }
    }
}

  结果:

folder:0        folder:0
folder:0        folder:30
folder:0        folder:60
folder:0        folder:90
folder:20       folder:0
folder:20       folder:30
folder:20       folder:60
folder:20       folder:90
folder:40       folder:0
folder:40       folder:30
folder:40       folder:60
folder:40       folder:90
folder:60       folder:0
folder:60       folder:30
folder:60       folder:60
folder:60       folder:90
folder:80       folder:0
folder:80       folder:30
folder:80       folder:60
folder:80       folder:90

  这就是需要的结果,不过注意,在这个方法运行期间,整个应用程序的内存占用几乎维持在初始的内存占用状态(7M),完全无视读取的文件的巨大尺寸。

  b. 注意事项

  不过要注意的是这个扩展是有部分问题的,因为XmlReader是用流的方式工作的,所以有些写法会产生不想要的结果,例如:

private static void ReadByReader_Incorrect()
{
    using (var reader = XmlReader.Create("test.xml"))
    {
        foreach (var item in from x in reader.Elements("root").First().Elements("folder")
                             where int.Parse(x.GetAttribute("name")) % 20 == 0
                             from y in x.Elements("folder")
                             where int.Parse(y.GetAttribute("name")) % 30 == 0
                             select string.Format("folder:{0}\tfolder:{1}", x.GetAttribute("name"), y.GetAttribute("name")))
        {
            Console.WriteLine(item);
        }
    }
}

  看看和正确版本的区别吧,少了let语句,表面上看语义是完全正确的,但是,实际上x和y这两个XmlReader是共用一个流的,也就是说y移动了流的位置之后,x的流位置也被移动了,所以运行出来的结果是:

folder:0        folder:0
folder:30       folder:30
folder:60       folder:60
folder:90       folder:90
folder:0        folder:0
folder:30       folder:30
folder:60       folder:60
folder:90       folder:90
folder:0        folder:0
folder:30       folder:30
folder:60       folder:60
folder:90       folder:90
folder:0        folder:0
folder:30       folder:30
folder:60       folder:60
folder:90       folder:90
folder:0        folder:0
folder:30       folder:30
folder:60       folder:60
folder:90       folder:90

  不难发现,x和y的读取出来的内容是完全一样的,这显然是这个扩展方法本身的缺陷。但是只要注意这些缺陷,还是可以在很大程度上简化编程的。

四、性能比较

  比较双方:XmlReader加上文中的两个扩展 VS Linq to Xml

1. 理解比较:

  • XmlReader使用只进流的方式工作,因此不需要占用很大的内存,Linq to Xml使用in-memory的方式工作,因此内存占用与Xml的大小有关。
  • XmlReader每次读取都要读取流所在的真实存储介质(测试中是文件),Linq to Xml的Load本身需要一次完整的加载,但是Load出来的结果是可以缓存下来的,而只有地查找就仅仅是一次内存查找了。

2. 测试  

  测试工具:CodeTimer

  测试数据:前面产生的250M左右的大型Xml,一个使用下面的code产生的小型xml(29K)  

static void CreateSmallXmlFile(string fileName)
{
    using (var writer = XmlWriter.Create(fileName,
            new XmlWriterSettings
            {
                Indent = true
            }))
    {
        writer.WriteStartDocument();
        writer.WriteStartElement("root");
        for (int i = 0; i < 10; i++)
        {
            writer.WriteStartElement("folder");
            writer.WriteAttributeString("name", i.ToString());
            for (int j = 0; j < 10; j++)
            {
                writer.WriteStartElement("folder");
                writer.WriteAttributeString("name", j.ToString());
                for (int k = 0; k < 10; k++)
                {
                    writer.WriteStartElement("file");
                    writer.WriteAttributeString("name", k.ToString());
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
        writer.WriteEndDocument();
    }
}

  测试代码:

static void ReadByReader(string fileName)
{
    using (var reader = XmlReader.Create(fileName))
        foreach (var item in
            from x in reader.Elements("root").First().Elements("folder")
            where x.GetAttribute("name") == "66"
            from y in x.Descendants("file")
            where y.GetAttribute("name") == "666"
            select y.GetAttribute("name"))
            ;
}

static void ReadByLinq_NoCache(string fileName)
{
    XDocument doc = XDocument.Load(fileName);
    foreach (var item in
        from x in doc.Root.Elements("folder")
        where (string)x.Attribute("name") == "66"
        from y in x.Descendants("file")
        where (string)y.Attribute("name") == "666"
        select (string)x.Attribute("name"))
        ;
}

static XDocument m_doc;
static void ReadByLinq_Cache(string fileName)
{
    if (m_doc == null)
        m_doc = XDocument.Load(fileName);
    foreach (var item in
        from x in m_doc.Root.Elements("folder")
        where (string)x.Attribute("name") == "66"
        from y in x.Descendants("file")
        where (string)y.Attribute("name") == "666"
        select (string)x.Attribute("name"))
        ;
}

//main方法
Console.Write("Ready:");
string fileName = "LargeXmlFile.xml";
int turnCount = 10;
Console.ReadLine();
CodeTimer.Time("Read by Xml Reader", turnCount, () => ReadByReader(fileName));
//CodeTimer.Time("Read by Linq to Xml (cache)", turnCount, () => ReadByLinq_Cache(fileName));
//CodeTimer.Time("Read by Linq to Xml (no cache)", turnCount, () => ReadByLinq_NoCache(fileName));
Console.ReadLine();  

  每次执行仅仅跑一个测试,避免不必要的误差,大xml文件仅跑10次,要不然时间太长,小xml文件跑1000次,结果如下:

  a. 大Xml文件测试结果:

Ready:
Read by Xml Reader
        Time Elapsed:           81,106ms
        Time Elapsed (one time):8,110ms
        CPU time:               81,062,500,000ns
        CPU time (one time):    8,106,250,000ns
        Gen 0:                  310
        Gen 1:                  5
        Gen 2:                  0

内存占用停留在8M

Ready:
Read by Linq to Xml (cache)
        Time Elapsed:           153,335ms
        Time Elapsed (one time):15,333ms
        CPU time:               77,687,500,000ns
        CPU time (one time):    7,768,750,000ns
        Gen 0:                  209
        Gen 1:                  171
        Gen 2:                  27

内存占用800余M,由于Windows动用了虚拟内存,所以即使Cache了Xml的内容,速度依然很糟糕

Ready:
Read by Linq to Xml (no cache)
        Time Elapsed:           1,100,787ms
        Time Elapsed (one time):110,078ms
        CPU time:               532,859,375,000ns
        CPU time (one time):    53,285,937,500ns
        Gen 0:                  1645
        Gen 1:                  1131
        Gen 2:                  124

内存10次上涨到800余M,笔记本被痛苦的折磨了以后,终于得到结果了。。。

  对大型Xml文件来说,XmlReader以极小的内存占用和极少的垃圾对象完胜。

  b. 小型Xml文件的测试代码

Console.Write("Ready:");
string fileName = "SmallXmlFile.xml";
int turnCount = 1000;
Console.ReadLine();
CodeTimer.Time("Read by Xml Reader", turnCount, () => ReadByReader(fileName));
//CodeTimer.Time("Read by Linq to Xml (cache)", turnCount, () => ReadByLinq_Cache(fileName));
//CodeTimer.Time("Read by Linq to Xml (no cache)", turnCount, () => ReadByLinq_NoCache(fileName));
Console.ReadLine();

  看看测试结果:

Ready:
Read by Xml Reader
        Time Elapsed:           1,835ms
        Time Elapsed (one time):1ms
        CPU time:               1,156,250,000ns
        CPU time (one time):    1,156,250ns
        Gen 0:                  19
        Gen 1:                  0
        Gen 2:                  0

内存几乎没增加,没有1代和2代的垃圾

Ready:
Read by Linq to Xml (cache)
        Time Elapsed:           258ms
        Time Elapsed (one time):0ms
        CPU time:               31,250,000ns
        CPU time (one time):    31,250ns
        Gen 0:                  0
        Gen 1:                  0
        Gen 2:                  0

速度飞快,并且没有垃圾产生(因为整个XDocument还被缓存引用着)

Ready:
Read by Linq to Xml (no cache)
        Time Elapsed:           1,589ms
        Time Elapsed (one time):1ms
        CPU time:               1,546,875,000ns
        CPU time (one time):    1,546,875ns
        Gen 0:                  74
        Gen 1:                  11
        Gen 2:                  0

Xml比较小,所以没有内存压力,但是有1代的垃圾  

  小Xml文件的测试结果表明,Linq to Xml的主要代价在加载Xml本身,查找的代价非常小,也就是有Cache时,整个过程的时间仅仅是No Cache的1/6,而XmlReader方式无法Cache。

    在同样No Cache的情况下,XmlReader的使用时间较长(多消耗15%),但是GC的压力相对较小(少消耗25%),同时CPU time也略小于Linq to Xml。

3. 结论

  如果仅仅是读取Xml的话,XmlReader的优势还是相当大的。当Xml很大时,XmlReader是唯一能保证内存不会成为制约因素的读取方式,即使Xml文件不是很大的情况下,XmlReader也不会落后于其他方式太多。要说缺点的话,最大的确定就是API并不怎么容易使用,除非自己添加扩展方法。

时间: 2024-10-11 16:22:59

C#操作XML(六)的相关文章

第一百二十六节,JavaScript,XPath操作xml节点

第一百二十六节,JavaScript,XPath操作xml节点 学习要点: 1.IE中的XPath 2.W3C中的XPath 3.XPath跨浏览器兼容 XPath是一种节点查找手段,对比之前使用标准DOM去查找XML中的节点方式,大大降低了查找难度,方便开发者使用.但是,DOM3级以前的标准并没有就XPath做出规范:直到DOM3在首次推荐到标准规范行列.大部分浏览器实现了这个标准,IE则以自己的方式实现了XPath. 一.IE中的XPath 在IE8及之前的浏览器,XPath是采用内置基于A

java操作XML文件

XML文件可以用来作为一种小型数据库存在,但更多时候在项目中都是用来当成配置文件用的,也就避免不了对XML文件的增上改查操作. 在java中,解析XML文件的方式大致分为两种:DOM解析,SAX解析 先来说DOM解析方式:xml解析器一次性把整个xml文档加载进内存,然后在内存中构建一颗Document的对象树,通过Document对象,得到树上的节点对象,通过节点对象访问(操作)到xml文档的内容. 用的较为多的是Dom4j工具(非官方)解析,简单介绍下Dom4j的使用,具体API文档请下载

Delphi操作XML - 冰雪傲骨

Delphi操作XMl,只要使用 NativeXml.我是用的版本是4.02.NativeXML的使用方法比较简单,但是功能很强大. XE2的话,要在simdesign.inc后面加上: // Delphi XE2 / 16 {$ifdef VER230} {$define D7UP} {$define D10UP} {$define D15UP} {$endif} 一.使用 1) Copy the NativeXml files from the NativeXml directory int

ActionScript 3操作XML 详解

AS3引入了E4X ,它是根据ECMAScript标准处理XML 数据的全新机制.这使得程序员在程序中无缝地操作XML.在AS3中可以使用XML字面值将XML数据直接写入代码,该字面值将被自动解析. 一.AS3中的XML入门 1.可以将XML直接写入代码 public var employeelist:XML=<employeelist> <employee> <name first="Conan" last="O'Brien" /&g

php操作xml小结

<?php #php操作xml,SimpleXMLElement类小结 header('Content-type:text/html;charset=utf-8;'); //1.构造函数 /* $xmlstring=<<<XML <?xml version="1.0" encoding="utf-8"?> <note  xmlns:b="http://www.w3school.com.cn/example/&quo

使用Dom4j操作XML数据

--------------siwuxie095 dom4j 是一个非常优秀的 Java XML 的 API, 用来读写 XML 文件 和操作 XML 数据 特点:性能优异.功能强大.极端易用 dom4j 的下载链接:http://www.dom4j.org/dom4j-1.6.1/ 将 dom4j-1.6.1.zip 解压一览: 工程名:TestDom4j 包名:com.siwuxie095.dom4j 类名:Test.java 打开资源管理器,在工程 TestDom4j 文件夹下,创建一个

dom4j操作xml对象

// 获取Documen对象 public static Document getDocument(String path) throws Exception{ // 解析器对象 SAXReader reader = new SAXReader(); // 解析 return reader.read(path); } // 回写(XMLWriter) public static void writeXml(Document document,String path) throws Excepti

转载:用Ant操作XML文件

1.14 用XMLTask操作XML(1) 本节作者:Brian Agnew 对于简单的文本搜索和替换操作,Ant的<replace>任务就够用了,但在现代Java框架中,用户更可能需要强大的XML操作能力来修改servlet描述符.Spring配置等. XMLTask是Ant外部任务,它提供了强大的XML编辑工具,主要用于在构建/部署过程中创建和修改XML文件. 使用XMLTask的好处如下? 与Ant的<replace>任务不同,XMLTask使用XPath提供识别XML文档各

简单操作XML

第一部分 什么是XML? XML, Extensible Markup Language ,可扩展标记语言,主要用途是描述和交换数据.它的一个用处是配置文件,用来保存数据库连接字符串.端口.IP.日志保存路径等参数.我们可以使用文本文件来保存文件,使用 key = value, key2 = value2 ,...... 的方式来保存数据.这样做的坏处是结构比较不规矩,读取起来也不方便,需要自行编写一长串的if / else 语句.为了解决这些问题,我们可以使用XML. XML定义了一组规则,即