决定何时使用XmlDocument和XmlReader

72

我正在优化一个自定义对象到XML序列化的工具,它已经全部完成且可用,这不是问题。

它通过将文件加载到XmlDocument对象中,然后递归遍历所有子节点来实现。

我想也许使用XmlReader而不是让XmlDocument加载/解析整个文件会更快,所以我也实现了那个版本。

算法完全相同,我使用一个包装器类来抽象处理XmlNodeXmlReader的功能。例如,GetChildren方法返回一个子XmlNode或一个子树XmlReader

所以我编写了一个测试驱动程序来测试两个版本,并使用非常庞大的数据集(大约有1,350个元素的900kb XML文件)。

然而,使用JetBrains dotTRACE,我发现XmlReader版本实际上比XmlDocument版本慢!当我迭代子节点时,XmlReader读取调用似乎涉及一些重要的处理。

所以我说所有这些来问这个:

XmlDocumentXmlReader的优缺点是什么?在什么情况下应该使用其中之一?

我的猜测是,在某个文件大小阈值上,XmlReader在性能和内存使用方面更加经济。然而,这个阈值似乎高于1MB。

我每次调用ReadSubTree来处理子节点:

public override IEnumerable<IXmlSourceProvider> GetChildren ()
{
    XmlReader xr = myXmlSource.ReadSubtree ();
    // skip past the current element
    xr.Read ();

    while (xr.Read ())
    {
        if (xr.NodeType != XmlNodeType.Element) continue;
        yield return new XmlReaderXmlSourceProvider (xr);
    }
}

这个测试适用于单层级别的许多对象(即宽而浅)- 但我想知道当XML深度和宽度增加时,XmlReader的表现如何?也就是说,我处理的XML非常像数据对象模型,一个父对象有许多子对象,以此类推:1..M..M..M

另外,我事先不知道正在解析的XML的结构,所以无法为其进行优化。


1
我一直想知道为什么会有 XmlDocument 和 XmlReader 这两个东西... - James Jones
实际上,除了XMLDocument和XMLReader之外,还有另一种选择。现在你可以使用LINQ to XML,但实际上XMLReader在大多数情况下更有效率。 - Tarik
2
等一下。你的 GetChildren 方法返回一个 XmlReader?你的意思是,每次处理子节点时都要调用 XmlReader.Create() 吗? - Robert Rossney
大多数使用XmlReader的代码都不使用ReadSubtree,所以这是一个错误的比较。此外,您需要为xr使用using块。 - John Saunders
在使用完XML文档后,将其设置为NULL或在using块内使用XML文档。 - Banketeshvar Narayan
5个回答

76

我通常不是从速度的角度来看待这个问题,而是从内存利用率的角度来看待。所有的实现在我使用它们的场景中都足够快(典型的企业集成)。

然而,我失败的地方在于没有考虑我正在处理的XML的普遍大小。如果您提前考虑,可以避免一些痛苦。

XML在加载到内存时往往会膨胀,至少使用像XmlDocumentXPathDocument这样的DOM读取器会如此。大约是10比1?具体数量很难量化,但例如,如果磁盘上有1MB,则在内存中的大小将为10MB或更多。

使用任何将整个文档完全加载到内存中的阅读器(XmlDocument / XPathDocument),进程可能会受到大对象堆碎片化的影响,这最终可能导致OutOfMemoryException(即使有可用内存),导致服务/进程无法使用。

由于大小超过85K的对象最终进入大对象堆,并且使用DOM读取器时大小爆炸了10:1,因此您可以看到,对于XML文档,仅需稍微多一点,就会从大对象堆中分配。

XmlDocument非常容易使用。 它唯一的缺点是将整个XML文档加载到内存中进行处理。看起来非常简单。

XmlReader是基于流的阅读器,因此它将保持进程内存利用率一般较平坦,但更难以使用。

XPathDocument往往是XmlDocument的一个更快的只读版本,但仍然存在内存“膨胀”的问题。


4
将XML文档加载到内存中并不会导致大型对象,无论文档有多大。然而,将XML作为字符串保留则会导致大型对象的出现!就垃圾回收器(GC)能否对内存进行碎片整理而言,个别对象的大小很重要,但是就内存使用情况而言,整个对象图的总大小才是关键。 - The Dag
1
顺便说一下,我刚刚对XDocument、XMLReader和XmlDocument进行了基准测试。在执行类似路径的操作时,它们分别需要0.004、0.001和0.692秒。 - micahhoover

11

XmlDocument是整个XML文档的内存表示。因此,如果您的文档很大,则会比使用XmlReader读取它消耗更多的内存。

这是在假设您使用XmlReader逐个读取和处理元素,然后丢弃它的情况下。如果您使用XmlReader并在内存中构造另一个中介结构,则会遇到同样的问题,而且您正在破坏它的目的。

请搜索“SAX与DOM”以了解有关处理XML的两种模型之间差异的更多信息。


1
烦人的是,完全没有任何指示表明一个文档在什么范围内变得“大”,以及XmlReader开始产生任何显著的大小优势。是1KB、1MB,还是更多?我相信答案是“视情况而定”,但如果完全没有线索,我们只能按照具体情况进行试验来确定这些事情,除非处理任意大的数据是必要的(那么XmlReader是明显的选择)。 - The Dag

4
另一个需要考虑的因素是,XMLReader在处理不完全格式化的XML时可能更加稳健。最近我创建了一个客户端来消费一个XML流,但是该流中某些元素中包含的URI未正确转义特殊字符。XMLDocument和XPathDocument完全无法加载XML,而使用XMLReader,我能够从流中提取所需的信息。

0

当XmlDocument变得越来越大时,它的速度会变慢,最终无法使用。但是,阈值的实际值取决于您的应用程序和XML内容,因此没有硬性规定。

如果您的XML文件可能包含大型列表(例如数万个元素),则应该使用XmlReader。


0

编码差异是因为混合了两种不同的度量。UTF-32每个字符需要4个字节,比单字节数据慢得多。

如果您查看大型(100K)元素测试,您会发现时间增加了约70毫秒,无论使用哪种加载方法。

这是一种(几乎)恒定的差异,具体是由于每个字符的开销引起的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接