上集回顾
上两集都介绍了Linq to Xml的相关用法,需要注意的一点是Linq to Xml是in-memory的处理方式,所以有多少节点,就要消耗多少内存,如果这个xml很大,但系统内存却有限的情况下怎么办哪?
设置目标
今天要做的是把某目录下的所有文件和目录输出到一个xml里面去,例如:
<?xml version="1.0" encoding="gb2312"?>
<folder name="bin">
<folder name="Debug">
<file name="ConsoleApplication6.exe" />
<file name="ConsoleApplication6.exe.config" />
<file name="ConsoleApplication6.pdb" />
<file name="ConsoleApplication6.vshost.exe" />
<file name="ConsoleApplication6.vshost.exe.config" />
<file name="ConsoleApplication6.vshost.exe.manifest" />
</folder>
<folder name="Release">
<file name="ConsoleApplication6.exe" />
<file name="ConsoleApplication6.exe.config" />
<file name="ConsoleApplication6.pdb" />
<file name="ConsoleApplication6.vshost.exe" />
<file name="ConsoleApplication6.vshost.exe.config" />
<file name="ConsoleApplication6.vshost.exe.manifest" />
</folder>
</folder>
当然这个需要可以作用于c盘根目录,也就是需要处理相当多的文件和目录。
使用Linq to Xml尝试
如果用前两集中介绍的Linq to Xml来做,可以很快地写出:
var di = new DirectoryInfo("..");
XDocument doc = new XDocument(GetFolderContent(di));
doc.Save(Console.Out);
static XElement GetFolderContent(DirectoryInfo di)
{
return new XElement("folder",
new XAttribute("name", di.Name),
from subDir in di.GetDirectories()
select GetFolderContent(subDir),
from file in di.GetFiles()
select new XElement("file", new XAttribute("name", file.Name)));
}
但是,需要注意一点,Linq to Xml是in-memory的方式工作的,也就是如果有1000级的目录嵌套,每级有100个子目录,那么就有100^1000个XElement在内存中创建出来,好吧,这么计算一下,整个过程需要多少内存吧,10的2000次方*每一个XElement消耗的内存,就算只有1个Byte,1G内存也只能处理10的30次方,所以要处理完这个场景,说需要的多少内存可以说是不可能达到的。
分析
上面的实现得益于Linq to Xml API的简易,但是却有受制于Linq to Xml的in-memory模型,要是有一种既可以受益于Linq to Xml的简易,又可以使用非in-memory的模型就两全其美了。
天下有这么好的事情吗?先不要急于下定论,让我们来查查msdn吧:如何:执行大型 XML 文档的流式转换
这里提到了一个XStreamingElement的类,仅仅从名称上,我们就可以知道,这个类是一个类似XElement的类型,但是它又是一个类似Stream的类,并不是in-memory的,当然具体怎么说还要看msdn。
msdn中明确说明:表示支持延迟流输出的 XML 树中的元素。
而且在备注中说到:如果从输入源(如文本文件)进行流式处理,则可以读取非常大的文本文件,并生成非常大的 XML 文档,同时保持较小的内存需求量。
也就是明确了XStreamingElement这个类本身就是(或类似)流式处理的,并不像普通的XElement的in-memory的处理方式。
实现
找到了XStreamingElement这个Linq to Xml的另类API之后,就可以实现了前面的目标了,XStreamingElement的用法与XElement十分相似,因此只需要把前面的方法稍作修改即可:
static XStreamingElement GetFolderContent(DirectoryInfo di)
{
return new XStreamingElement("folder",
new XAttribute("name", di.Name),
from subDir in di.GetDirectories()
select GetFolderContent(subDir),
from file in di.GetFiles()
select new XElement("file", new XAttribute("name", file.Name)));
}
不过,需要额外修改一下外面的调用方式:
var di = new DirectoryInfo(@"C:\");
GetFolderContent(di).Save(Console.Out);
这里必须要用XStreamingElement的Save方法,否则延迟求解的特性可能会失效。
本集介绍了Linq to Xml中的异类:XStreamingElement,既得益于Linq to Xml的简易,又拥有streaming的小内存特性,在操作超大xml是这两个特性可以让工作事半功倍。
但是,如果一个需要流处理的需求,并且部署环境没有安装.net 3.5 framework的话,怎么办哪,这使我们不得不回到xml reader和xml writer的时代,下集将介绍如何使用这两者。