高效解析XML

作者:Ping Guo、Julie Basu、Mark Scardina和K. Karun

为你的Java应用程序选择合适的XML解析技术

随着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">
    <title>XML in a Nutshell</title>
    <author>Elliotte Rusty Harold, W. Scott Means</author>
    <price>39.95</price>
  </book>
  <book id="121">
    <title>Who Moved My Cheese</title>
    <author>Spencer, M.D. Johnson, Kenneth H. Blanchard</author>

    <price>19.95</price>
  </book>
</catalog>

DOM解析

DOM是一个基于树型的解析技术,它在内存中构建起一棵完整的解析树。它可以实现对整个XML文档的全面、动态访问。

图 1显示了DOM解析模型的树型结构。文档是所有DOM树的根,这个根有至少一个子节点,即根元素,它是示例代码中的catalog元素。另一个节点是DocumentType,用于DTD说明,在我们的示例中没有定义。catalog元素有子节点,它的子节点也有自己的子节点。子节点可以是元素、文本、注释、处理指令以及类似的信息。

A DOM tree
图 1: DOM树

下面的例子显示了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。

SAX reports a document to an application
图 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很少被选择用于操作或修改文档。

Oracle和XML

Oracle XML开发人员工具包为Java、C和C++开发提供XML解析器。每一种都提供为企业实现的DOM和SAX接口。还提供了一个用于Java的StAX解析器的技术预览。这些组件可以从Oracle Technology Network的XML技术中心otn.oracle.com/tech/xml下载获得。

那些只需要单遍读取内容的应用程序可以从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文档读取并将该事件返回给该应用程序。

An application requests StAX to report an event
图 3: 应用程序要求StAX报告一个事件

StAX提供了用于创建StAX读写器的工具,所以应用程序可以使用StAX接口而无需参考特定实现的细节。

与DOM和SAX不同,StAX指定了两个解析模型:指针模型,如SAX,它简单地返回事件;迭代程序模型,它以对象形式返回事件,提供更符合习惯的接口,但需要额外的对象创建开销。下面的StAX解析API说明给出了每种模型的例子。

下面的示例使用指针模型从上述的XML书籍目录中打印出书名信息。

 
XMLStreamReader reader = XMLInputFactory
  .newInstance().createXMLStreamReader(
    new FileInputStream("books.xml"));
while(reader.hasNext()) {
	 int eventType = reader.next();
	 if (eventType == XMLEvent
.START_ELEMENT && reader.getLocalName()
.equals("title")) {
    reader.next();
    System.out.println(reader.getText());
	 }
} 

在这个示例中,reader建立后,应用程序通过reader.next()方法调用请求下一个事件。这使得StAX解析器将指针移到下一个事件的位置。如果这个事件指明了一个名为“title”的元素的开始,则该应用程序代码将再一次调用reader.next(),向前移动指针,然后通过reader.getText()方法调用获得title元素的文本。

下面的示例使用迭代程序模型,在这种方法下事件以对象的形式被返回。

 
XMLEventReader eventReader =
  XMLInputFactory.newInstance()
    .createXMLEventReader(
       new FileInputStream("books.xml"));
while(eventReader.hasNext()) {
	 XMLEvent event = eventReader.next();
	 if (event instanceof StartElement && 	 
  ((StartElement)event).getLocalName()
.equals("title")) 
  {
  System.out.println( ((Characters)eventReader.next())
  .getData());
	}
}

在这个示例中,应用程序请求下一个事件,使StAX解析器前进到下一个事件位置,并返回相应的事件对象。应用程序可以使用返回书名信息的getData()方法通过事件对象访问内容。

下一步

访问OTN的XML中心
otn.oracle.com/tech/xml

下载Oracle XDK
otn.oracle.com/tech/xml

从Oracle大学学习更多XML知识
oracle.com/education/om 关键字: XML

StAX指针模型的性能与SAX解析相当。然而,使用StAX应用程序可以控制解析,使得代码更易于编写和维护。StAX还提供了易于使用的迭代程序模型,但在这种情况下,创建事件对象要付出性能代价。SAX要求应用程序跟踪其在文档中的位置,而StAX与此不同,由于它具有返回所请求事件的能力而使应用程序无需进行这种跟踪。这些例子没有显示出StAX的过滤功能,这个功能比SAX的强大得多。

与DOM相比,StAX有一些与SAX相同的缺点,那就是缺乏全面的导航支持。StAX中对文档的前向遍历比SAX中的容易,因为应用程序可以控制它获取哪个事件以及获取时间。

StAX修改文档的能力与SAX的相似,因为都创建了一个新的文档。StAX的指针模型和迭代程序模型提供写出API,但如果你希望不仅仅是单遍转换的话,那么文档修改还是相当困难。

StAX解析可满足SAX应用程序的大部分要求,所以如果一个应用程序很适合使用SAX解析,那么它也从采用StAX中受益。而且,当应用程序为了提高性能而需要利用数据流模型,同时维持对名字空间的全面支持时,StAX解析是最佳选择。最后,为了处理多个输入,就象在一个导入的模式集合中那样,应用程序可以轻松地向多个StAX解析器请求事件并将它们放入一个单一环境中,而无需启动多个线程。StAX在Web服务和JAX-RPC等新领域尤为有用,在这些领域中,所有这些特性都是必需的。

选择正确的解析模型

本文已经说明了用于Java的三种标准解析技术,描述了它们的工作原理以及各自的优点、缺点和适用的应用程序。本文的要点可以总结为几条简单规则:

  • 当你的应用程序需要不断地导航、修改文档或随机地一次访问整个文档时,使用DOM解析。

  • 当你需要简单的只读数据流并且希望一个体现成熟标准的强健的实施方案时,就使用SAX解析。

  • 当你要求完整名字空间或多文档支持或需要一个对象接口时,将StAX用于数据流应用程序。

不同的解析模式都有自己的存在原因,而随着基于分布式服务的应用程序的发展,优化的性能将成为成功的重要因素。

Ping Guo (ping.guo@oracle.com) 和Julie Basu (julie.basu@oracle.com) 是Oracle Java和XML技术小组的成员。Mark Scardina (mark.scardina@oracle.com) K. Karun (k.karun@oracle.com) 是Oracle CORE和XML开发小组的成员。

XML解析技术一览

技术优点 缺点最适于...
DOM解析
  • 易于使用
  • 丰富的API集合,可用于轻松地导航
  • 整棵树加载到内存,允许对XML文档进行随机访问
  • 整个XML文档必须一次解析完
  • 将整棵树加载到内存成本较高
  • 一般的DOM节点对于必须为所有节点创建对象的对象类型绑定不太理想
  • 需要修改XML文档的应用程序或XSLT应用程序(不可用于只读XML的应用程序)
SAX解析
  • 无需将整个文档加载到内存,因而内存消耗少
  • 推模型允许注册多个ContentHandler
  • 没有内置的文档导航支持
  • 不能够随机访问XML文档
  • 不支持在原地修改XML
  • 不支持名字空间作用域
  • 只从XML读取数据的应用程序(不可用于操作或修改XML文档)
StAX解析
  • 提供两种解析模型,分别出于简单和性能的考虑
  • 应用程序控制解析,轻松支持多个输入
  • 强大的过滤功能提供高效数据获取
  • 没有内置的导航支持
  • 不能够随机访问XML文档
  • 不能在原地修改XML
  • 需要数据流模型和支持名字空间的应用程序(不可用于操作或修改XML文档)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值