`
helpbs
  • 浏览: 1154780 次
文章分类
社区版块
存档分类
最新评论

使用 StAX 解析 XML,第 1 部分: Streaming API for XML (StAX) 简介

 
阅读更多

http://www.ibm.com/developerworks/cn/xml/x-stax1.html


探究其基于指针的 API,它把 XML 作为标记(或事件)流拉出

Peter Nehrer(pnehrer@ecliptical.ca), 自由撰稿人, 独立咨询顾问

简介:Streaming API for XML (StAX) 是用 Java™ 语言处理 XML 的最新标准。作为一种面向流的方法,无论从性能还是可用性上都优于其他方法,如 DOM 和 SAX。本系列分为 3 部分,本文是第 1 部分,简要介绍了 StAX 及其处理 XML 的基于指针的 API。

查看本系列更多内容

本文的标签:使用stax解析xml

发布日期:2007 年 3 月 02 日
级别:中级
访问情况 :7550 次浏览
评论:0(查看|添加评论- 登录)

平均分 5 星 共 11 个评分平均分 (11个评分)
为本文评分

StAX 概述

从一开始,Java API for XML Processing (JAXP) 就提供了两种方法来处理 XML:文档对象模型(DOM)方法是用标准的对象模型表示 XML 文档;Simple API for XML (SAX) 方法使用应用程序提供的事件处理程序来处理 XML。JSR-173 提出了一种面向流的新方法:Streaming API for XML (StAX)。其最终版本于 2004 年 3 月发布,并成为了 JAXP 1.4(将包含在即将发布的 Java 6 中)的一部分。

如其名称所暗示的那样,StAX 把重点放在上。实际上,StAX 与其他方法的区别就在于应用程序能够把 XML 作为一个事件流来处理。将 XML 作为一组事件来处理的想法并不新颖(事实上 SAX 已经提出来了),但不同之处在于 StAX 允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。

StAX 实际上包括两套处理 XML 的 API,分别提供了不同程度的抽象。基于指针的 API 允许应用程序把 XML 作为一个标记(或事件)流来处理;应用程序可以检查解析器的状态,获得解析的上一个标记的信息,然后再处理下一个标记,依此类推。这是一种低层 API,尽管效率高,但是没有提供底层 XML 结构的抽象。较为高级的基于迭代器的 API 允许应用程序把 XML 作为一系列事件对象来处理,每个对象和应用程序交换 XML 结构的一部分。应用程序只需要确定解析事件的类型,将其转换成对应的具体类型,然后利用其方法获得属于该事件的信息。

基本原理

为了使用这两类 API,应用程序首先必须获得一个具体的XMLInputFactory。根据传统的 JAXP 风格,要用到抽象工厂模式;XMLInputFactory类提供了静态的newInstance方法,它负责定位和实例化具体的工厂。配置该实例可设置定制或者预先定义好的属性(其名称在类 XMLInputFactory 中定义)。最后,为了使用基于指针的 API,应用程序还要通过调用某个createXMLStreamReader方法获得一个XMLStreamReader。如果要使用基于事件迭代器的 API,应用程序就要调用createXMLEventReader方法获得一个XMLEventReader(如清单 1 所示)。


清单 1. 获取和配置默认的XMLInputFactory
                
// get the default factory instance
XMLInputFactory factory = XMLInputFactory.newInstance();
// configure it to create readers that coalesce adjacent character sections
factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
XMLStreamReader r = factory.createXMLStreamReader(input);
// ...

XMLStreamReaderXMLEventReader都允许应用程序迭代底层的 XML 流。两种方法的差别在于如何公开解析后的 XML InfoSet 信息片段。XMLStreamReader就像一个指针,指在刚刚解析过的 XML 标记的后面,并提供了方法获得更多关于该标记的信息。这种方法节约内存,因为不用创建新的对象。但是,业务应用程序开发人员可能会发现XMLEventReader更直观一些,因为它实际上就是一个标准的 Java 迭代器,将 XML 变成了事件对象流。每个事件对象都封装了它所表示的特定 XML 结构固有的信息。本系列的第二部分将详细讨论这种基于事件迭代器的 API。

使用哪种风格的 API 取决于具体情况。和基于指针的 API 相比,基于事件迭代器的 API 具有更多的面向对象特征。因此更便于应用于模块化的体系结构,因为当前的解析器状态反映在事件对象中,应用程序组件在处理事件的时候不需要访问解析器/读取器。此外,还可以使用XMLInputFactorycreateXMLEventReader(XMLStreamReader)方法从XMLStreamReader创建XMLEventReader

StAX 还定义了一种序列化器 API,Java 标准 XML 处理支持中一直缺少的一种特性。和解析一样,也包含两种风格的流式 API:处理标记的底层XMLStreamWriter和处理事件对象的高层XMLEventWriterXMLStreamWriter提供了写入单个 XML 记号(比如开始和关闭标记或者元素属性)的方法,不检查这些标记是否格式良好。另一方面,XMLEventWriter允许应用程序向输出中添加完整的 XML 事件。第 3 部分将详细讨论 StAX 序列化器 API。

为什么使用 StAX?

开始学习一种新的处理 XML 的 API 之前,可能要问是否值得这样做。事实上,StAX 所采用的基于拉的方法和其他方法相比有一些突出的优点。首先,不管使用哪种 API 风格,都是应用程序调用读取器(解析器)而不是相反。通过保留解析过程的控制权,可以简化调用代码来准确地处理它预期的内容。或者发生意外时停止解析。此外,由于该方法不基于处理程序回调,应用程序不需要像使用 SAX 那样模拟解析器的状态。

StAX 仍然保留了 SAX 相对于 DOM 的优点。通过把重心从结果对象模型转移到解析流本身,从理论上说应用程序能够处理无限的 XML 流,因为事件固有的临时性,不会在内存中累积起来。对于那些使用 XML 作为消息传递协议而非表示文档内容的那些应用程序尤其重要,比如 Web 服务或即时消息应用程序。比方说,如果只是将其转换成特定于应用程序的对象模型然后就将其丢弃,那么为 Web 服务路由器 servlet 提供一个 DOM 就没有多少用处。使用 StAX 直接转化成应用程序模型效率更高。对于 Extensible Messaging and Presence Protocol(XMPP)客户机,根本不能使用 DOM,因为 XMPP 客户机/服务器流是随着用户输入的消息实时生成。等待流的关闭标签(以便最终建立 DOM)就意味着等待整个会话结束。通过把 XML 作为一系列的事件来处理,应用程序能够以最合适的方式响应每个事件(比如显示收到的即时消息等等)。

由于其双向性,StAX 也支持链式处理,特别是在事件层上。接收事件(无论什么来源)的能力被封装在 XMLEventConsumer(XMLEventWriter 的扩展)接口中。因此,可以模块化地编写应用程序从 XMLEventReader(也是一个普通的迭代器,可以按迭代器处理)读取和处理 XML 事件、然后传递给事件消费者(如果需要可以进一步扩展处理链)。在第 2 部分将看到,也可使用应用程序提供的筛选器(实现了 EventFilter 接口的类)来定制 XMLEventReader 或者使用 EventReaderDelegate 修饰已有的 XMLEventReader。

总而言之,和 DOM 以及 SAX 相比,StAX 使应用程序更贴近底层的 XML。使用 StAX,应用程序不仅可以建立需要的对象模型(而不需要处理标准 DOM),而且可以随时这样做,而不必等到解析器回调。

下一节将深入讨论基于指针的 API 以及如何有效地使用它处理 XML 流。

基于指针的 API

如果使用基于指针的 API,应用程序通过在 XML 标记流中移动逻辑指针来处理 XML。基于指针的解析器实质上是一个状态机,在事件的驱动下从一个良好定义的状态转移到另一个状态。这里的触发事件是随着应用程序使用适当的方法推动解析器在标记流中前进而解析出来的 XML 标记。在每个状态,都可使用一组方法获得上一个事件的信息。一般来说,并非每个状态下都能使用所有的方法。

使用基于指针的方法,应用程序首先必须通过调用其createXMLStreamReader方法从XMLInputFactory得到XMLStreamReader。该方法有多个版本,支持不同类型的输入。比方说,可以创建XMLStreamReader解析plain java.io.InputStreamjava.io.Reader或者 JAXP Source(javax.xml.transform.Source)。从理论上说,后一种办法很容易和其他 JAXP 技术交互,比如 SAX 和 DOM。


清单 2. 创建XMLStreamReader解析InputStream
                
URL url = new URL(uri);
InputStream input = url.openStream();
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader r = factory.createXMLStreamReader(uri, input);
// process the stream
// ...
r.close();
input.close();

XMLStreamReader接口基本上定义了基于指针的 API(虽然标记常量在其超类型XMLStreamConstants接口中定义)。之所以称为基于指针,是因为读取器就像是底层标记流上的指针。应用程序可以沿着标记流向前推进指针并分析当前指针所在位置的标记。

XMLStreamReader提供了多种方法导航标记流。为了确定当前指针所指向的标记(或事件)的类型,应用程序可以调用getEventType()。该方法返回接口XMLStreamConstants中定义的一个标记常量。移动到下一个标记,应用程序可以调用next()。该方法也返回解析的标记的类型,如果接着调用getEventType()则返回的值相同。只有当方法hasNext()返回 true 时(就是说还有其他标记需要解析)才能调用该方法(以及其他移动读取器的方法)。


清单 3. 使用XMLStreamReader处理 XML 的常用模式
                
// create an XMLStreamReader
XMLStreamReader r = ...;
try {
 int event = r.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.START_DOCUMENT:
 // add cases for each event of interest
 // ...
 }

 if (!r.hasNext())
 break;
 
 event = r.next();
 }
} finally {
 r.close();
}

还与其他几种方法可以移动readernextTag()方法将跳过所有的空白、注释或处理指令,直到遇到START_ELEMENTEND_ELEMENT。该方法在解析只含元素的内容时很有用,如果在发现标记之前遇到非空白文本(不包括注释或处理指令),就会抛出异常。getElementText()方法返回元素的开始和关闭标签(即START_ELEMENTEND_ELEMENT)之间的所有文本内容。如果遇到嵌套的元素就会抛出异常。

请注意,这里的 “标记” 和 “事件” 可以互换使用。虽然基于指针的 API 的文档说的是事件,但把输入源看成标记流很方便。而且不容易造成混乱,因为还有一整套基于事件的 API(那里的事件是真正的对象)。不过,XMLStreamReader的事件本质上并非都是标记。比方说,START_DOCUMENTEND_DOCUMENT事件不需要对应的标记。前一个事件是解析开始之前发生,后者则在没有更多解析工作要做的时候发生(比如解析完成最后一个元素的关闭标签之后,读取器处于END_ELEMENT状态,但是如果没有发现更多的标记需要解析,读取器就会切换到END_DOCUMENT状态)。

处理 XML 文档

在每个解析器状态,应用程序都可通过可用的方法获得相关信息。比如,无论当前是什么类型的事件,getNamespaceContext()getNamespaceURI()方法可以获得当前有效的名称空间上下文和名称空间 URI。类似的,getLocation()可以获得当前事件的位置信息。方法hasName()hasText()可以分别判断当前事件是否有名称(比如元素或属性)或文本(比如字符、注释或 CDATA)。方法isStartElement()isEndElement()isCharacters()isWhiteSpace()可以方便地确定当前事件的性质。最后,方法 require(int,String,String) 可以声明预期的解析器状态;除非当前事件是指定的类型,并且本地名和名称空间(如果给出的话)与当前事件匹配,否则该方法将抛出异常。


清单 4. 如果当前事件是START_ELEMENT使用有关的属性方法
                
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
 System.out.println("Start Element: " + reader.getName());
 for(int i = 0, n = reader.getAttributeCount(); i < n; ++i) {
 QName name = reader.getAttributeName(i);
 String value = reader.getAttributeValue(i);
 System.out.println("Attribute: " + name + "=" + value);
 }
}

创建之后,XMLStreamReader将从START_DOCUMENT状态开始(即getEventType()返回START_DOCUMENT)。处理标记的时候应考虑到这一点。和迭代器不同,不需要先移动指针(使用next())来进入合法的状态。同样地,当读取器转换到最终状态END_DOCUMENT之后,应用程序也不应再移动它。在这种状态下,hasNext()方法将返回 false。

START_DOCUMENT事件提供了获取关于文档本身信息的方法,如getEncoding()getVersion()isStandalone()。应用程序也可调用getProperty(String)获得命名属性的值,不过一些属性仅在特定状态做了定义(比方说,如果当前事件是 DTD,则属性javax.xml.stream.notationsjavax.xml.stream.entities分别返回所有的符号和实体声明)。

START_ELEMENTEND_ELEMENT事件中,可以使用和元素名称以及名称空间有关的方法(如getName()getLocalName()getPrefix()getNamespaceXXX()),在START_ELEMENT事件中还可使用与属性有关的方法(getAttributeXXX())。

ATTRIBUTENAMESPACE也被识别为独立的事件,虽然在解析 典型的 XML 文档时不会用到。但是当ATTRIBUTENAMESPACE节点作为 XPath 查询结果返回时可以使用。

和基于文本的事件(如CHARACTERSCDATACOMMENTSPACE),可使用各种getTextXXX()方法取得文本。可以分别使用getPITarget()getPIData()检索PROCESSING_INSTRUCTION的目标和数据。ENTITY_REFERENCEDTD也支持getText()ENTITY_REFERENCE还支持getLocalName()

解析完成后,应用程序关闭读取器并释放解析过程中获得的资源。请注意这样并没有关闭底层的输入源。

清单 5 提供了一个完整的例子,使用基于指针的 API 处理 XML 文档。首先取得XMLInputFactory的默认实例并创建一个XMLStreamReader解析给定的输入流。然后不断检查读取器的状态,根据当前事件的类型报告某些信息(比如在START_ELEMENT状态下报告元素名及元素属性)。最后,遇到END_DOCUMENT时关闭读取器。


清单 5. 使用XMLStreamReader解析 XML 文档的完整例子
                
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader r = factory.createXMLStreamReader(input);
try {
 int event = r.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.START_DOCUMENT:
 out.println("Start Document.");
 break;
 case XMLStreamConstants.START_ELEMENT:
 out.println("Start Element: " + r.getName());
 for(int i = 0, n = r.getAttributeCount(); i < n; ++i)
 out.println("Attribute: " + r.getAttributeName(i) 
 + "=" + r.getAttributeValue(i));
 
 break;
 case XMLStreamConstants.CHARACTERS:
 if (r.isWhiteSpace())
 break;
 
 out.println("Text: " + r.getText());
 break;
 case XMLStreamConstants.END_ELEMENT:
 out.println("End Element:" + r.getName());
 break;
 case XMLStreamConstants.END_DOCUMENT:
 out.println("End Document.");
 break;
 }
 
 if (!r.hasNext())
 break;

 event = r.next();
 }
} finally {
 r.close();
}

XMLStreamReader的高级用法

通过调用XMLInputFactory的带有基本读取器的createFilteredReader方法和一个应用程序定义的筛选器(即实现StreamFilter的类实例),可以创建筛选过的XMLStreamReader。导航筛选过的读取器时,读取器每次移动到下一个标记之前都会询问筛选器。如果筛选器认可了当前事件,就将其公开给筛选过的读取器。否则跳过这个标记并检查下一个,依此类推。这种方法可以让开发人员创建一个仅处理解析内容子集的基于指针的 XML 处理程序,并与针对不同的扩展的内容模型的筛选器结合使用。

执行更复杂的流操作,可以创建StreamReaderDelegate的子类并重写合适的方法。然后使用这个子类的实例包装基本XMLStreamReader,从而为应用程序提供一个修改过的基本 XML 流的视图。可通过这种技术对 XML 流执行简单的转换,比如筛掉或者替换特定的标记,甚至增加新的标记。

清单 6 用定制的StreamReaderDelegate包装了基本XMLStreamReader,重写了next()方法来跳过COMMENTPROCESSING_INSTRUCTION事件。使用该读取器时,应用程序不用担心会遇到这种类型的标记。


清单 6. 使用定制的StreamReaderDelegate筛选注释和处理指令
                
URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader(uri, input);
XMLStreamReader fr = new StreamReaderDelegate(r) {
 public int next() throws XMLStreamException {
 while (true) {
 int event = super.next();
 switch (event) {
 case XMLStreamConstants.COMMENT:
 case XMLStreamConstants.PROCESSING_INSTRUCTION:
 continue;
 default:
 return event;
 }
 }
 }
};

try {
 int event = fr.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.COMMENT:
 case XMLStreamConstants.PROCESSING_INSTRUCTION:
 // this should never happen
 throw new IllegalStateException("Filter failed!");
 default:
 // process XML normally
 }

 if (!fr.hasNext())
 break;

 event = fr.next();
 }
} finally {
 fr.close();
}

input.close();

基于指针处理之外的其他技术

可以看到,基于指针的 API 主要是为了提高效率。所有的状态信息可以直接从流读取器获得,不需要创建额外的对象。非常适用于性能和低内存占用至关重要的应用程序。

人们早就认识到了拉式 XML 解析的好处。事实上,StAX 本身源于一种称为XML Pull Parsing的方法。XML Pull Parser API 类似于 StAX 所提供的基于指针的 API,可以通过分析解析器的状态获得上一个解析事件的信息,然后移动到下一个,依此类推。但没有提供基于事件迭代器的 API。这是一种非常轻型的方法,特别适合资源受限的环境,比如 J2ME。但是,很少有实现提供企业级特性如验证,因此 XML Pull 一直未受到企业 Java 开发人员的关注。

基于以往拉式解析器实现的经验,StAX 的创建者选择了在基于指针的 API 之外增加一种面向对象的 API。虽然XMLEventReader接口看起来似乎很简单,但是基于事件迭代器的方法具有一个基于指针的方法不具备的重要优点。通过将解析器事件变成一级对象,从而让应用程序可以采用面向对象的方式处理它们。这样做有助于模块化和不同应用程序组件之间的代码重用。


清单 7. 使用 StAXXMLEventReader解析 XML
                
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = inputFactory.createXMLEventReader(input);
try {
 while (reader.hasNext()) {
 XMLEvent e = reader.nextEvent();
 if (e.isCharacters() && ((Characters) e).isWhiteSpace())
 continue;
 
 out.println(e);
 }
} finally {
 reader.close();
}

结束语

本文介绍了 StAX 及其基于指针的 API。第 2 部分将深入讨论事件迭代器 API。


参考资料

学习

获得产品和技术

  • Sun 的JAXP 项目页:可以找到各种 JAXP 版本的下载链接。

讨论

关于作者

Peter Nehrer 是一名专长于基于 Eclipse 的企业解决方案和 Java EE 应用程序的软件顾问。他创建了 Ecliptical Software Inc.,并且是一些和 Eclipse 有关的开放源码项目的贡献者。他拥有从马萨诸塞州大学阿默斯特校区获得的计算机科学硕士学位。


分享到:
评论

相关推荐

    stax2-api:Stax扩展API,Java拉解析API(针对Xml的Streaming Api)

    概述 Stax2 API是标准 API(... Stax2 API还包含许多API类的局部参考实现,下org.codehaus.stax2.ri :目的是要使它尽可能无痛的Java Stax的解析器,以实现全Stax2支持。 用法 Maven依赖项定义为: &lt;dependency&gt;

    stax2-api-4.2.1.jar

    用来解析XML文件的jar包。Streaming API for XML (StAX) 的基于事件迭代器 API 无论在性能还是在可用性上都有其他 XML 处理方法所不及的独到之处。使用前请先解压

    stax2-api-3.1.1.jar

    用来解析XML文件的jar包。Streaming API for XML (StAX) 的基于事件迭代器 API 无论在性能还是在可用性上都有其他 XML 处理方法所不及的独到之处。使用前请先解压

    stax2-api-3.1.1-sources.jar

    stax2-api-3.1.1-sources.jar文件,下载使用,用来解析XML文件的jar包。Streaming API for XML (StAX) 的基于事件迭代器 API 无论在性能还是在可用性上都有其他 XML 处理方法所不及的独到之处。

    Android读写XML.docx

    举例来说,Java 的 Simple API for XML (SAX) 和 Document Object Model (DOM) 在 Android 上都是可用的,这些 API 多年以来一直都是 Java 技术的一部分,较新的 Streaming API for XML (StAX) 在 Android 中并不...

    fluentxml4j:Java中用于XML解析,序列化,XPath查询和转换的fluent API

    FluentXML4J-Java中XML的流畅API XML解析,序列化XPath查询和转换,无需样板代码 ...通过XPath查询org.w3c.dom.Document以获取String,Boolean,Number,org.w3c.dom.Element或Node,并使用Java 8 Streaming API

    jackson-jar

    1) Streaming API 又称Incremental parsing/generation, 受StAX API启发,以非关联递增方式读写json内容。 通过 org.codehaus.jackson.JsonParser读取,通过org.codehaus.jackson.JsonGenerator写入。 2) Tree...

    woodstox-core-asl-4.0.6.jar

    Woodstox是一个快速开源且符合StAX(STreaming Api for Xml processing)规范的XML处理器(相当于写入,序列化)。使用前请先解压一下

    jdk-9.0.1_doc-all 最新版

    Defines the Java API for XML Processing (JAXP), the Streaming API for XML (StAX), the Simple API for XML (SAX), and the W3C Document Object Model (DOM) API. java.xml.bind Defines the Java ...

    stac:StAC(用于CSS的流API)

    StAC(CSS的流API)。 在Java中,我们已经有了SAC( ),它是一个类似于SAXCSS推送解析器。 该项目实现了CSS的提取解析API,类似于StAX(XML的Streaming API)。

    超级有影响力霸气的Java面试题大全文档

    当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例...

    java 面试题 总结

    1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括...

    xs4s:Scala的XML流包括FS2cats支持

    xs4s:Scala的XML流包括FS2cats支持

    JavaEE 5.0 Tutorial.pdf

    For Sun Java System Application Server 9.1 Contents Preface ...............................................................................................................................................

Global site tag (gtag.js) - Google Analytics