一 开发XML应用程序常用的几种模型
通常我们使用根据以下这些模型创建的API 来分析和操纵XML结构,这些模型可以是基于对象(基于树)的,如文档对象模型(Document Object Model,DOM);也可以是基于事件(基于流、推模型)的,如 Simple API for XML(SAX)。
JDOM试图用 DOM 和 SAX 20% 的功能来满足 80% 的用户需求,它使用 SAX 和 DOM 解析器,作为一组相对较小的 Java 类被实现。而Java API for XML Parsing(JAXP)和MSXML提供了使用 DOM、SAX等处理XML文档的通用接口。
二 DOM、SAX及其比较与选择
用于读取和操作 XML 文件的标准是文档对象模型DOM。
DOM为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构,然后您的代码就可以使用 DOM 接口来操作这个树结构。您可以遍历树以了解原始文档包含了什么,您可以删除树的几个部分,还可以重新排列树和添加新的分支,等等。遗憾的是,因为DOM 方法涉及读取整个文件并将该文件存储在一个树结构中,而这样可能是低效的、缓慢的,并且很消耗资源:DOM 构建整个文档驻留内存的树。如果文档很大,就会要求有极大的内存。DOM 创建表示原始文档中每个东西的对象,包括元素、文本、属性和空格。如果您只需关注原始文档的一小部分,那么创建那些永远不被使用的对象是极其浪费的。DOM 解析器必须在您的代码取得控制权之前读取整个文档。对于非常大的文档,这会引起显著的延迟。这些仅仅是由文档对象模型的设计引起的问题;撇开这些问题,DOM API 是解析 XML 文档非常有用的方法。
一种替代技术就是SAX。相比于文档对象模型DOM,SAX 是读取和操作 XML 数据的更快速、更轻量的方法。SAX 允许您在读取文档时处理它,从而不必等待整个文档被存储之后才采取操作。它不涉及 DOM 所必需的开销和概念跳跃。SAX API是一个基于事件的 API,适用于处理数据流,即随着数据的流动而依次处理数据。SAX API 在其解析您的文档时发生一定事件的时候会通知您。在您对其响应时,您不作保存的数据将会被抛弃。
在使用DOM的情况下,解析器做了绝大多数事情, 读入XML文档, 在这基础之上创建对象模型,然后给你一个对这个对象的引用(一个 Document对象),因而你可以操作使用它。SAX没有期待解析器去做这么多工作,所有SAX 要求的是解析器应该读入XML文档,同时根据所遇到的XML文档的标签向一个事件处理程序发出一系列事件,比如元素开始和元素结束,而事件处理器则处理该信息:你要自己写一个XML文档处理器类(XML document handler class)来处理这些事件,这意味着使所有标签事件有意义还有用你自己的对象模型创建对象。所以你要完成: 控制所有XML文档信息的自定义对象模型。 一个监听SAX事件(事件由SAX解析器读取你的XML文档时产生)的文档处理器,还有解释这些事件创建你自定义对象模型中的对象。如果你的对象模型简单的话那么SAX在运行时会非常快。在这种情况下,它会比DOM快,因为它忽略了为你的信息创建一个树形对象模型的过程。从另一方面来说,你必须写一个SAX 文档处理器来解释所有的SAX事件(这会是一件很繁重的工作)。基于事件的API 消除了在内存中构造树的需要,却不允许开发人员实际更改原始文档中的数据。所以原始的文档仍然保留完好无损;但是 SAX 提供了操作数据的手段,而后数据可以引入另一个进程或文档。
要使用 XML 文档做任何事情,你都必须读取其中的信息。做这个工作的应用程序称为解析器。它设计用于分析文档(这里是指 XML 文件),以及做一些特定于该信息的事情。在诸如 SAX 这样基于事件的 API 中,解析器将向某种监听器发送事件。在诸如 DOM 这样基于树的 API 中,解析器将在内存中构造一颗数据树。解析器的两种类型分别是:非验证和验证。非验证解析器是适用于格式良好(well-formed)文档的解析器。 它读取每个信息单元,并将其添加到文档 ―― 或者在 SAX 应用程序的情况下处理事件,而不管实际的结构和内容如何。 另一方面,验证解析器根据已定义的语法检查 XML 文档的内容和结构。 有时,这个语法是文档类型定义(Document Type Definition,DTD)的形式,但是它更可能在 XML Schema 文档中定义。在任一种情况下,解析器都会检查文档,以确保每个元素和属性都已定义,并且包含正确类型的内容。例如,您可以指定每个 order(订单都有一个status(状态)。如果在没有语法定义的情况下尝试创建文档,验证解析器将会提示错误。已经由验证解析器检验过的文档被认为是有效的文档。
SAX 的几个特征解决了 DOM 的问题:
SAX 解析器向您的代码发送事件。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,它会告诉您。您可以决定什么事件对您重要,而且可以决定要创建什么类型的数据结构以保存来自这些事件的数据。如果您没有显式地保存来自某个事件的数据,它就被丢弃。 SAX 解析器根本不创建任何对象,它只是将事件传递给您的应用程序。如果希望基于那些事件创建对象,这将由您来完成。
SAX 解析器在解析开始的时候就开始发送事件。当解析器发现文档开始、元素开始和文本等时,代码会收到一个事件。您的应用程序可以立即开始生成结果;您不必一直等到整个文档被解析完毕。更妙的是,如果您只查找文档中某些内容,代码一旦找到所要找的东西就可以抛出一个异常。该异常会停止 SAX 解析器,然后代码用它找到的数据做它需要做的任何事。
公平而言,SAX 解析器也有些问题引人关注:
DOM 所提供的丰富的标准功能在 SAX 中是没有的。
SAX 事件是无状态的。当 SAX 解析器在 XML 文档中发现文本时,它就向您的代码发送一个事件。该事件仅仅给您发现的文本;它不告诉您什么元素包含那个文本。如果您想知道这一点,则必须自己编写状态管理代码。
SAX 事件不是持久的。如果应用程序需要一个数据结构来对 XML 文档建模,则必须自己编写那样的代码。如果您需要从 SAX 事件访问数据,并且没有把那个数据存储在代码中,那么您不得不再次解析该文档。
考虑XML 代码片断
<?xml version="1.0"?>
<samples>
<server>UNIX</server>
<monitor>color</monitor>
</samples>
DOM 处理是如何工作的
使用 DOM 时,数据以树状结构的形式被加载到内存中。例子的文档在 DOM 中将表示为节点,如下所示:
矩形框表示元素节点,椭圆表示文本节点。
DOM 使用父子关系。例如,在这个例子中,samples 是具有五个孩子的根元素:三个文本节点(空白),以及两个元素节点 server 和 monitor。
要认识到的一件重要事情是,server 和 monitor 节点实际上具有 null 值。相反,它们具有文本节点(UNIX 和 color)作为孩子。
SAX 处理是如何工作的
SAX 在读取 XML 流的同时处理它们,这很像以前的自动收报机纸带(ticker tape)。
而分析这个代码片断的 SAX 处理器一般情况下将产生以下事件:
Start document
Start element (samples)
Characters (white space)
Start element (server)
Characters (UNIX)
End element (server)
Characters (white space)
Start element (monitor)
Characters (color)
End element (monitor)
Characters (white space)
End element (samples)
SAX API 允许开发人员捕捉这些事件并对它们作出反应。
SAX 处理涉及以下步骤:
创建一个事件处理程序。
创建 SAX 解析器。
向解析器分配事件处理程序。
解析文档,同时向事件处理程序发送每个事件。
[什么类型的SAX事件被SAX解析器抛出了哪? 这些事件实际上是非常简单的。SAX会对每一个开始标签抛出事件,对每一个结束标签也是如此。它对#PCDATA和 CDATA 部分同样抛出事件。你的文档处理器 (对这些事件的监听器)要解释这些事件同时还要在他们基础之上创建你自定义的对象模型。 你的文档处理器必须对这些事件做出解释,同时这些事件发生的顺序是非常重要的。SAX同时也对processing instructions, DTDs, comments, 抛出事件. 但是它们在概念上是一样的, 你的解析器要解释这些事件(还有这些事件的发生顺序)以及使他们有意义。]
基于树的处理的优点和缺点
DOM 以及广义的基于树的处理具有几个优点。
首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像 SAX 那样是一次性的处理。DOM 使用起来也要简单得多。
另一方面,在内存中构造这样的树涉及大量的开销。大型文件完全占用系统内存容量的情况并不鲜见。此外,创建一棵 DOM 树可能是一个缓慢的过程。
基于事件的处理的优点和缺点
这 种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存 储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX 还比它的替代者 DOM 快许多。
另一方面,由于应用程序没有以任何方式存储数据,使用 SAX 来更改数据或在数据流中往后移是很困难的。
如何在 SAX 和 DOM 之间选择
选择 DOM 还是选择 SAX,这取决于下面几个因素:
应用程序的目的:如果打算对数据作出更改并将它输出为 XML,那么在大多数情况下,DOM 是适当的选择。并不是说使用 SAX 就不能更改数据,但是该过程要复杂得多,因为您必须对数据的一份拷贝而不是对数据本身作出更改。
[由 于 SAX 涉及在读取数据时分析数据(而不是在存储之后再分析),您可能认为没有办法在分析数据之前更改数据。这就是SAX 2.0 版中新引入的 XMLFilter 要解决的问题。认识到能够将 SAX 流“链接”在一起,从而能够在数据到达最终目的地之前有效地操作它们。基本上,它像下面这样工作: 创建 XMLFilter,这通常是一个简单的类; 创建 XMLFilter 的一个实例,并将它的父亲设置为通常负责解析文件的 XMLReader; 将过滤器的内容处理程序设置为通常的内容处理程序; 解析文件。 过滤器介于 XMLReader 和内容处理程序之间。]
一旦解析了 XML 文档,还需要多次访问那些数据吗?如果您需要回过头来访问 XML 文件的已解析版本,DOM 可能是正确的选择。而 SAX 事件被触发时,如果您以后需要它,则由您(开发人员)自己决定以某种方式保存它。如果您需要访问不曾保存的事件,则必须再次解析该文件。而 DOM 自动保存所有的数据。
数据容量: 对于大型文件,SAX 是更好的选择。
数据将如何使用:如果只有数据中的少量部分会被使用,那么 使用 SAX 来将该部分数据提取到应用程序中可能更好。SAX 不会为源文件中的每个东西创建对象;您要确定什么是重要的。使用 SAX,您要检查每个事件以了解它是否与您的需要有关,然后相应地处理它。更妙的是,一旦找到您正在寻找的东西,您的代码就会抛出一个异常来完全停止 SAX 解析器。 另一方面,如果您知道自己以后会回头引用已处理过的大量信息,那么 SAX 也许不是恰当的选择。
对速度的需要: SAX 实现通常要比 DOM 实现更快。
SAX 和 DOM 不是相互排斥的,记住这点很重要。您可以使用 DOM 来创建 SAX 事件流,也可以使用SAX 来创建 DOM 树。事实上,用于创建 DOM 树的大多数解析器实际上都使用 SAX 来完成这个任务!