该Java教程适用于JDK 8。本页面描述的示例和实践不利用后续版本引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及以后版本中更新的语言功能的摘要,请参见Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息,请参见JDK发行说明。
StAX API公开了用于迭代、基于事件的处理XML文档的方法。XML文档被视为一系列经过过滤的事件,并且信息集状态可以以过程化的方式存储。此外,与SAX不同,StAX API是双向的,可以同时读取和写入XML文档。
StAX API实际上是两个不同的API集:游标API和迭代器API。这两个API集将在本课程的后面详细介绍,但它们的主要特点如下。
顾名思义,StAX游标API表示一个游标,您可以使用该游标从头到尾遍历XML文档。该游标一次只能指向一个元素,并且始终向前移动,通常一次移动一个信息集元素。
两个主要的游标接口是XMLStreamReader和XMLStreamWriter。XMLStreamReader包含用于从XML信息模型中检索的所有可能信息的访问器方法,包括文档编码、元素名称、属性、命名空间、文本节点、起始标记、注释、处理指令、文档边界等等;例如:
public interface XMLStreamReader { public int next() throws XMLStreamException; public boolean hasNext() throws XMLStreamException; public String getText(); public String getLocalName(); public String getNamespaceURI(); // ... other methods not shown }
您可以调用XMLStreamReader上的方法,如getText和getName,以获取当前游标位置的数据。XMLStreamWriter提供了与StartElement和EndElement事件类型相对应的方法;例如:
public interface XMLStreamWriter { public void writeStartElement(String localName) throws XMLStreamException; public void writeEndElement() throws XMLStreamException; public void writeCharacters(String text) throws XMLStreamException; // ... other methods not shown }
游标API在许多方面与SAX相似。例如,可用于直接访问字符串和字符信息的方法,并且可以使用整数索引来访问属性和命名空间信息。与SAX一样,游标API方法将XML信息作为字符串返回,从而减少了对象分配的要求。
StAX迭代器API将XML文档流表示为一组离散的事件对象。这些事件由应用程序拉取并由解析器按照源XML文档中的顺序提供。
基本迭代器接口称为XMLEvent,并且每个事件类型在 XMLEvent表中都有子接口。用于读取迭代器事件的主要解析器接口是XMLEventReader,用于写入迭代器事件的主要接口是XMLEventWriter。XMLEventReader接口包含五个方法,其中最重要的是nextEvent,它返回XML流中的下一个事件。XMLEventReader实现了java.util.Iterator,这意味着从XMLEventReader返回的结果可以被缓存或传递到可以与标准Java迭代器一起工作的例程中,例如:
public interface XMLEventReader extends Iterator { public XMLEvent nextEvent() throws XMLStreamException; public boolean hasNext(); public XMLEvent peek() throws XMLStreamException; // ... }
类似地,在迭代器API的输出方面,你有:
public interface XMLEventWriter { public void flush() throws XMLStreamException; public void close() throws XMLStreamException; public void add(XMLEvent e) throws XMLStreamException; public void add(Attribute attribute) throws XMLStreamException; // ... }
事件类型
描述
StartDocument
报告一组XML事件的开始,包括编码、XML版本和独立属性。
StartElement
报告元素的开始,包括任何属性和命名空间声明;还提供了访问开始标签的前缀、命名空间URI和本地名称的方法。
EndElement
报告元素的结束标记。如果已经在相应的StartElement上显式设置了命名空间,那么在这里可以重新调用已经过期的命名空间。
Characters
对应于XML的CData部分和CharacterData实体。请注意,可忽略的空格和重要的空格也被报告为Character事件。
EntityReference
字符实体可以作为离散事件报告,应用程序开发人员可以选择解析或保持未解析。默认情况下,实体将被解析。或者,如果不想将实体作为事件报告,则可以替换并将替换文本报告为Characters。
ProcessingInstruction
报告底层处理指令的目标和数据。
Comment
返回注释的文本。
EndDocument
报告一组XML事件的结束。
DTD
报告与流相关联的DTD(如果有)的java.lang.String信息,并提供了一种返回在DTD中找到的自定义对象的方法。
Attribute
属性通常作为StartElement事件的一部分报告。但是,有时候希望将属性作为独立的Attribute事件返回;例如,当命名空间作为XQuery或XPath表达式的结果返回时。
Namespace
与属性一样,命名空间通常作为StartElement的一部分报告,但有时希望将命名空间作为离散的Namespace事件报告。
请注意,只有在处理的文档中包含DTD时,才会创建DTD、EntityDeclaration、EntityReference、NotationDeclaration和ProcessingInstruction事件。
作为事件迭代器API如何映射XML流的示例,考虑以下XML文档:
<?xml version="1.0"?> <BookCatalogue xmlns="http://www.publishing.org"> <Book> <Title>瑜伽科学:瑜伽的科学</Title> <ISBN>81-40-34319-4</ISBN> <Cost currency="INR">11.50</Cost> </Book> </BookCatalogue>
该文档将被解析为十八个主要和次要事件,如下表所示。请注意,次要事件通常从主要事件而不是直接访问。
#
元素/属性
事件
1
version="1.0"
开始文档
2
isCData = false data = "\n" IsWhiteSpace = true
字符
3
qname = BookCatalogue:http://www.publishing.org attributes = null namespaces = {BookCatalogue" -> http://www.publishing.org"}
开始元素
4
qname = Book attributes = null namespaces = null
开始元素
5
qname = Title attributes = null namespaces = null
开始元素
6
isCData = false data = "Yogasana Vijnana: the Science of Yoga\n\t" IsWhiteSpace = false
字符
7
qname = Title namespaces = null
结束元素
8
qname = ISBN attributes = null namespaces = null
开始元素
9
isCData = false data = "81-40-34319-4\n\t" IsWhiteSpace = false
字符
10
qname = ISBN 命名空间 = null
EndElement
11
qname = Cost 属性 = {"currency" -> INR} 命名空间 = null
StartElement
12
isCData = false 数据 = "11.50\n\t" IsWhiteSpace = false
Characters
13
qname = Cost 命名空间 = null
EndElement
14
isCData = false 数据 = "\n" IsWhiteSpace = true
Characters
15
qname = Book 命名空间 = null
EndElement
16
isCData = false 数据 = "\n" IsWhiteSpace = true
Characters
17
qname = BookCatalogue:http://www.publishing.org 命名空间 = {BookCatalogue" -> http://www.publishing.org"}
EndElement
18
EndDocument
在这个例子中有几个重要的事情需要注意:
事件的创建顺序与文档中遇到的相应的XML元素的顺序一致,包括元素的嵌套、元素的打开和关闭、属性的顺序、文档的开始和结束等等。
与正确的XML语法一样,所有的容器元素都有相应的开始和结束事件;例如,每个StartElement都有相应的EndElement,即使是空元素也是如此。
Attribute事件被视为次要事件,并从其对应的StartElement事件中访问。
类似于Attribute事件,Namespace事件也被视为次要事件,在事件流中会出现两次,并且可以从其对应的StartElement和EndElement事件中访问。
所有元素都指定了Character事件,即使这些元素没有字符数据。类似地,Character事件可以跨事件分割。
StAX解析器维护一个命名空间堆栈,其中保存了当前元素及其祖先元素定义的所有XML命名空间的信息。命名空间堆栈通过javax.xml.namespace.NamespaceContext接口暴露,并可以通过命名空间前缀或URI进行访问。
现在可以合理地问一下,“我应该选择哪个API?我应该创建XMLStreamReader还是XMLEventReader的实例?为什么会有两种类型的API?”
StAX规范的作者针对三类开发者:
库和基础设施开发者:创建应用服务器、JAXM、JAXB、JAX-RPC等实现;需要高效、低级别的API,且对扩展性要求最小。
Java ME开发者:需要小巧、简单的拉取解析库,对扩展性需求较小。
Java Platform, Enterprise Edition (Java EE)和Java Platform, Standard Edition (Java SE)开发者:需要干净、高效的拉取解析库,同时需要灵活地读取和写入XML流,创建新的事件类型,以及扩展XML文档元素和属性。
鉴于这些广泛的开发类别,StAX的作者认为定义两个小巧、高效的API比重载一个更大且必然更复杂的API更有用。
在选择Cursor和Iterator API之前,你应该注意一些使用Iterator API而不能使用Cursor API的事情:
从XMLEvent子类创建的对象是不可变的,可以在数组、列表和映射中使用,并且即使解析器已经移动到后续事件,仍然可以通过应用程序传递。
您可以创建XMLEvent的子类型,这些子类型可以是完全新的信息项,也可以是现有项的扩展,但具有附加的方法。
您可以以比使用光标API更简单的方式向XML事件流中添加和删除事件。
同样,在做选择时,请牢记以下一些建议:
如果您正在为特别受内存限制的环境编程,例如Java ME,您可以使用光标API生成更小、更高效的代码。
如果性能是您的最高优先级,例如在创建低级别库或基础设施时,光标API更高效。
如果您想创建XML处理流水线,请使用迭代器API。
如果您想修改事件流,请使用迭代器API。
如果您希望您的应用程序能够处理可插拔的事件流处理,请使用迭代器API。
一般而言,如果您对两者没有明确偏好,建议使用迭代器API,因为它更灵活、可扩展,从而使您的应用程序具有“未来性”。