# StAX API

> 原文： [https://docs.oracle.com/javase/tutorial/jaxp/stax/api.html](https://docs.oracle.com/javase/tutorial/jaxp/stax/api.html)

StAX API 公开了用于迭代，基于事件的 XML 文档处理的方法。 XML 文档被视为过滤的一系列事件，并且信息集状态可以以过程方式存储。此外，与 SAX 不同，StAX API 是双向的，可以读取和写入 XML 文档。

StAX API 实际上是两个不同的 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 信息作为字符串返回，从而最大限度地减少了对象分配要求。

## 迭代器 API

StAX **迭代器** API 将 XML 文档流表示为一组离散事件对象。这些事件由应用程序提取，并由解析器按照在源 XML 文档中读取它们的顺序提供。

基本迭代器接口称为 `XMLEvent` ， [XMLEvent 表](#indexterm-18)中列出的每种事件类型都有子接口。用于读取迭代器事件的主要解析器接口是 `XMLEventReader` ，用于编写迭代器事件的主要接口是 `XMLEventWriter` 。 `XMLEventReader` 接口包含五种方法，其中最重要的是 `nextEvent` ，它返回 XML 流中的下一个事件。 `XMLEventReader` 实现了 `java.util.Iterator` ，这意味着来自 `XMLEventReader` 的返回可以被缓存或传递到可以与标准 Java Iterator 一起使用的例程中;例如：

```
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;
    // ...
}

```

### 迭代器事件类型

`XMLEvent` Types Defined in the Event Iterator API 
 

| 事件类型 | 描述 |
| :-- | :-- |
| `StartDocument` | 报告一组 XML 事件的开始，包括编码，XML 版本和独立属性。 |
| `StartElement` | 报告元素的开始，包括任何属性和名称空间声明;还可以访问开始标记的前缀，名称空间 URI 和本地名称。 |
| `EndElement` | 报告元素的结束标记。如果已在相应的 `StartElement` 上明确设置了超出范围的命名空间，则可以在此处调用它们。 |
| `Character` | 对应于 XML `CData` 部分和 `CharacterData` 实体。请注意，可忽略的空白区域和显着的空白区域也会报告为 `Character` 事件。 |
| `EntityReference` | 可以将字符实体报告为离散事件，然后应用程序开发人员可以选择解析或通过未解析的事件。默认情况下，实体已解析。或者，如果您不想将实体报告为事件，则可替换替换文本并将其报告为`字符`。 |
| `ProcessingInstruction` | 报告基础处理指令的目标和数据。 |
| `评论` | 返回注释的文本。 |
| `EndDocument` | 报告一组 XML 事件的结束。 |
| `DTD` | 报告 `java.lang.String` 有关与流关联的 DTD 信息（如果有），并提供返回 DTD 中找到的自定义对象的方法。 |
| `属性` | 属性通常作为 `StartElement` 事件的一部分报告。但是，有时需要将属性作为独立的`属性`事件返回;例如，当 `XQuery` 或 `XPath` 表达式返回命名空间时。 |
| `命名空间` | 与属性一样，命名空间通常作为 `StartElement` 的一部分进行报告，但有时需要将命名空间报告为离散`命名空间`事件。 |

请注意， `DTD` ， `EntityDeclaration` ， `EntityReference` ， `NotationDeclaration` 和 `ProcessingInstruction` 事件仅在文档中创建正在处理包含 DTD。

### 事件映射示例

作为事件迭代器 API 如何映射 XML 流的示例，请考虑以下 XML 文档：

```
<?xml version="1.0"?>
<BookCatalogue xmlns="http://www.publishing.org">
    <Book>
        <Title>Yogasana Vijnana: the Science of Yoga</Title>
        <ISBN>81-40-34319-4</ISBN>
        <Cost currency="INR">11.50</Cost>
    </Book>
</BookCatalogue>

```

该文档将被分析为十八个主要和次要事件，如下表所示。请注意，大括号（ `{}` ）中显示的辅助事件通常是从主事件而不是直接访问的。

Example of Iterator API Event Mapping 
  

| ＃ | 元素/属性 | 事件 |
| :-- | :-- | :-- |
| 1 | `version="1.0"` | `StartDocument` |
| 2 | `isCData = false data = "\n" IsWhiteSpace = true` | `Character` |
| 3 | `qname = BookCatalogue:http://www.publishing.org attributes = null namespaces = {BookCatalogue" -&gt; http://www.publishing.org"}` | `StartElement` |
| 4 | `qname = Book attributes = null namespaces = null` | `StartElement` |
| 五 | `qname = Title attributes = null namespaces = null` | `StartElement` |
| 6 | `isCData = false data = "Yogasana Vijnana: the Science of Yoga\n\t" IsWhiteSpace = false` | `Character` |
| 7 | `qname = Title namespaces = null` | `EndElement` |
| 8 | `qname = ISBN attributes = null namespaces = null` | `StartElement` |
| 9 | `isCData = false data = "81-40-34319-4\n\t" IsWhiteSpace = false` | `Character` |
| 10 | `qname = ISBN namespaces = null` | `EndElement` |
| 11 | `qname = Cost attributes = {"currency" -&gt; INR} namespaces = null` | `StartElement` |
| 12 | `isCData = false data = "11.50\n\t" IsWhiteSpace = false` | `Character` |
| 13 | `qname = Cost namespaces = null` | `EndElement` |
| 14 | `isCData = false data = "\n" IsWhiteSpace = true` | `Character` |
| 15 | `qname = Book namespaces = null` | `EndElement` |
| 16 | `isCData = false data = "\n" IsWhiteSpace = true` | `Character` |
| 17 | `qname = BookCatalogue:http://www.publishing.org namespaces = {BookCatalogue" -&gt; http://www.publishing.org"}` | `EndElement` |
| 18 |  | `EndDocument` |

在这个例子中有几点需要注意：

*   事件按照文档中遇到相应 XML 元素的顺序创建，包括元素嵌套，元素的打开和关闭，属性顺序，文档开始和文档结束等。

*   与适当的 XML 语法一样，所有容器元素都有相应的开始和结束事件;例如，每个 `StartElement` 都有一个相应的 `EndElement` ，即使对于空元素也是如此。

*   `属性`事件被视为次要事件，并从相应的 `StartElement` 事件中访问。

*   与`属性`事件类似，`名称空间`事件被视为次要事件，但出现两次并且在事件流中可访问两次，首先来自其对应的 `StartElement` ，然后从他们对应的 `EndElement` 。

*   `为所有元素指定了字符`事件，即使这些元素没有字符数据。类似地， `Character` 事件可以跨事件分割。

*   StAX 解析器维护一个名称空间堆栈，该堆栈包含有关为当前元素及其祖先定义的所有 XML 名称空间的信息。通过 `javax.xml.namespace.NamespaceContext` 接口公开的命名空间堆栈可以通过命名空间前缀或 URI 访问。

## 在 Cursor 和 Iterator API 之间进行选择

在这一点上提出“我应该选择什么 API？我应该创建 `XMLStreamReader` 或 `XMLEventReader` 的实例吗？为什么还有两种 API 呢？“

### 发展目标

StAX 规范的作者针对三种类型的开发人员：

*   **图书馆和基础设施开发人员**：创建应用服务器，JAXM，JAXB，JAX-RPC 和类似的实现;需要高效，低级的 API，并且可扩展性要求最低。

*   **Java ME 开发人员**：需要小型，简单的拉解析库，并且具有最小的可扩展性需求。

*   **Java 平台，企业版（Java EE）和 Java 平台，标准版（Java SE）开发人员**：需要干净，高效的拉解析库，需要灵活地读取和写入 XML 流，创建新的事件类型，并扩展 XML 文档元素和属性。

鉴于这些广泛的开发类别，StAX 作者认为定义两个小而有效的 API 更有用，而不是重载一个更大且必然更复杂的 API。

### 比较 Cursor 和 Iterator API

在光标和迭代器 API 之间进行选择之前，您应该注意一些可以使用游标 API 无法执行的迭代器 API：

*   从 `XMLEvent` 子类创建的对象是不可变的，可以在数组，列表和映射中使用，甚至在解析器转移到后续事件之后也可以通过您的应用程序传递。

*   您可以创建 `XMLEvent` 的子类型，它们是完全新的信息项或现有项的扩展，但使用其他方法。

*   您可以使用比游标 API 更简单的方式在 XML 事件流中添加和删除事件。

同样，在做出选择时，请记住一些一般性建议：

*   如果您正在为特定于内存的环境（如 Java ME）编程，则可以使用游标 API 生成更小，更高效的代码。

*   如果性能是您的最高优先级 - 例如，在创建低级库或基础结构时 - 游标 API 更有效。

*   如果要创建 XML 处理管道，请使用迭代器 API。

*   如果要修改事件流，请使用迭代器 API。

*   如果希望应用程序能够处理事件流的可插入处理，请使用迭代器 API。

*   通常，如果您没有强烈的偏好，建议使用迭代器 API，因为它更灵活和可扩展，从而“面向未来”您的应用程序。