随着xml越来越广泛地被采用,高效解析XML文档也变得越来越重要。高效地解析XML数据非常重要,尤其是对于那些要处理大量数据的应用程序,这种技术尤为重要。不正确的解析会导致过度的内存消耗和过长的处理时间,从而有损于可伸缩性。
XML解析器有多种类型。哪一种最适合你呢?本文研究三种流行的用于java的XML解析技术,告诉你如何根据你应用程序的要求来选择正确的方法。
XML解析器将一个未经处理的序列字符串作为输入,并对它执行一些特定的操作。首先它检查XML数据是否符合句法规则,确保开始标记与其有匹配的结束标记,并且没有重叠的元素。大多数解析器还根据文档类型定义(Document Type Definition,DTD)或XML Schema进行确认,核实其结构和内容是你所指定的。最后,解析输出通过编程API提供对XML文档内容的访问。
有三种用于Java的流行XML解析技术:
文档对象模型(Document Object Model,DOM),一个来自W3C的成熟标准。
用于XML的简单API(Simple API for XML,SAX),第一个被广泛采用的用Java编写的XML API,是一个事实上的标准。
用于XML的数据流API(Streaming API for XML,StAX),JSR-173中采用的一个很有前途的新解析模型。
这些技术中的每一种都有其优点和缺点。
下面的XML文档books.xml描述了一个书籍目录,并在本文中作为例子使用:
<catalog
<!—Sample —
<book id="101"
<titleXML in a Nutshell</title
<authorElliotte Rusty Harold, W. Scott Means</author
<price39.95</price
</book
<book id="121"
<titleWho Moved My Cheese</title
<authorSpencer, M.D. Johnson, Kenneth H. Blanchard</author
<price19.95</price
</book
</catalog
DOM解析
DOM是一个基于树型的解析技术,它在内存中构建起一棵完整的解析树。它可以实现对整个XML文档的全面、动态访问。
图 1显示了DOM解析模型的树型结构。文档是所有DOM树的根,这个根有至少一个子节点,即根元素,它是示例代码中的catalog元素。另一个节点是DocumentType,用于DTD说明,在我们的示例中没有定义。catalog元素有子节点,它的子节点也有自己的子节点。子节点可以是元素、文本、注释、处理指令以及类似的信息。
下面的例子显示了DOM API的用法。这个示例代码从上一个XML文档中打印出一个目录下所有书籍的名称。
DOMParser parser = new DOMParser();
parser.parse("books.xml");
Document document = parser.getDocument();
NodeList nodes =
document.getElementsByTagName("title");
while(int i = 0;
i< nodes.length(); i ++) {
Element titleElem =
(Element)nodes.item(i);
Node childNode =
titleElem.getFirstChild();
if (childNode instanceof Text) {
System.out.PRintln("Book title is: "
+ childNode.getNodeValue());
}
}
这个程序获得XML文件名,建立DOM树,通过使用getElementsByTagName()方法找出各个title元素的所有DOM元素节点。最后,通过重复操作title元素的列表并使用getFirstChild()方法检查,确证第一个子节点在元素的开始和结束标记之间包含了文本,打印出与每个title元素相关的文本信息。
可以看到,使用DOM非常简单。你可以随机地访问XML文档,因为整个树都构建在内存中。通过DOM API可以修改这些节点,例如增加一个子节点或修改、删除一个节点。
虽然内存树结构提供了很好的导航支持,但仍有一些解析策略问题需要考虑。首先,整个XML文档必须一次解析完成,不可能只做部分解析。其次,在内存中加载整个文档和构建完整树结构成本很高,尤其当文档非常大的时候。典型地,DOM树的容量比文档容量要大一个数量级,所以它要消耗大量内存。第三,一般的DOM节点类型在互操作性上有优势,但对于对象类型绑定也许不是最好的。
某些类型的应用程序要比其它类型的应用程序更适合采用DOM解析。当应用程序需要随机访问XML文档时很适合采用DOM解析。一个比较好的例子是需要在处理模板时需要重复查找整个文件的XSL处理器。因为DOM使你能够更新文档,因此对于需要修改数据的应用程序,如XML编辑器来说DOM解析也很方便。
SAX解析
SAX是一个用于处理XML的事件驱动的“推”模型。它不是W3C标准,但它是一个得到了广泛认可的API,大多数SAX解析器在实现的时候都遵循标准。SAX解析器不象DOM那样建立一个整个文档的树型表示,而是在读取文档时激活一系列的事件。这些事件被推给事件处理器,而事件处理器则提供对文档内容的访问。事件处理器有三种基本类型:
用于访问XML DTD内容的DTDHandler;
用于低级访问解析错误的ErrorHandler;
用于访问文档内容的最普遍类型ContentHandler。
图 2显示了SAX解析器如何通过一个回调机制报告事件。解析器读取输入文档并在处理文档时将每个事件推给MyContentHandler。
图 2:SAX以一系列事件的方式向应用程序报告文档。
下面的例子和前一个DOM例子做同样的事情: 打印出一本书的名称信息。
首先,编写一个ContentHandler实现类,该类建立在DefaultHandler类基础上,并替换你感爱好的事件类别所用的方法。该代码抛弃来自DefaultHandler类的其他事件。定制的ContentHandler类提供回调方法,必须处理状态治理,操作开始元素事件、结束元素事件和字符事件--为所有元素而不仅仅是title元素。
public class MyContentHandler extends DefaultHandler {
boolean isTitle;
public void startElement(String uri, String localName,
String qName, Attributes atts) {
if (localName.equals("title"))
isTitle = true;
}
public void endElement(String uri, String localName,
String qName) {
if(localName.equals("title"))
isTitle = false;
}
public void characters(char[ ] chars, int start, int length) {
if(isTitle)
System.out.println(new String(chars, start, length));
}
}
其次,为SAX解析器配置你的定制ContentHandler,然后该解析器开始处理XML文档。该解析器产生相应的一些事件并在从头至尾读取文档时将这些事件推给ContentHandler。
SAXParser saxParser = new SAXParser();
MyContentHandler myHandler = new
MyContentHandler();
saxParser.setContentHandler(myHandler);
saxParser.parse(new File("books.xml"));
与DOM相比,SAX解析器提供更佳的性能优势。它提供对XML文档内容的有效低级访问。SAX模型最大的优点是内存消耗小,因为整个文档无需一次加载到内存中,这使SAX解析器可以解析大于系统内存的文档。另外,你无需象在DOM中那样为所有节点创建对象。最后,SAX“推”模型可用于广播环境,在这里可以注册多个ContentHandler,并行接收事件,而不是在一个管道中只能一个接一个地处理。
SAX的缺点是你必须实现处理所有到来的事件的事件处理程序。你必须在你的应用程序代码中维护这个事件状态。因为SAX解析器不能交流元信息,如DOM的父/子支持,所以你必须跟踪解析器处在文档层次的哪个位置。这样,你的文档越复杂,你的应用逻辑就越复杂。虽然没有必要一次将整个文档加载到内存中,但SAX解析器仍然需要解析整个文档,这点和DOM一样。
也许SAX面临的最大问题是它没有内置如XPath所提供的那样的导航支持。再加上它的单遍解析,这就意味着它不支持随机访问。这个限制也表现在名字空间上: 对有继续名字空间的元素也不做注解。这些限制使SAX很少被选择用于操作或修改文档。
那些只需要单遍读取内容的应用程序可以从SAX解析中大大受益。很多B2B和EAI应用程序将XML用作封装格式,接收端用这种格式简单地接收所有数据。这就是SAX明显优于DOM的地方,前者高效因而获得的高吞吐率。SAX 2.0 有一个内置的过滤机制,可以很轻松地输出一个文档子集或进行简单的文档转换。最后,SAX解析对根据DTD和XML Schema进行确认非常有用。
实际上,Oracle在内部使用SAX解析器来完成这种确认,比起DOM来使用更少的内存并获得更高的效率。
StAX解析
StAX是一个令人激动的新解析技术,和SAX一样,使用一种事件驱动的模型。然而,StAX不使用SAX的推模型,而是使用“拉”模型进行事件处理。而且StAX解析器不使用回调机制,而是根据应用程序的要求返回事件。StAX还提供了用户友好的API用于读入和写出。
尽管SAX向ContentHandler返回不同类型的事件,但StAX却将它的事件返回给应用程序,甚至可以以对象的形式提供事件。
图 3显示了当应用程序要求一个事件时,StAX解析器根据需要从XML文档读取并将该事件返回给该应用程序。