1. 流式解析入门
在 Java 中,处理 XML 历来有两种主要方式。
我们已经知道,DOM(Document Object Model) 会在内存中构建文档树。遍历和编辑元素都很方便,但对于大文件来说成本过高:内存很快就会耗尽。
相对地,SAX(Simple API for XML) 顺序处理文件,遇到标签时触发事件。它非常省内存,可以处理超大文档。但编写事件处理器不够直观,而且无法在结构中“回退”。
StAX(Streaming API for XML) 是一种折中的方案。它同样是流式的(类似 SAX),但给了程序员更多控制权:在需要时我们自己“拉取”事件。这种方式称为pull 解析,能让代码更清晰、更灵活。
StAX 简介
StAX(Streaming API for XML) 是 Java 的现代 XML 流式解析器,出现在 JDK 6+。
核心思想:pull 解析(“拉取式”解析)。
与 SAX 的 push 模型(解析器主动回调你的方法)不同,在 StAX 中你自己控制过程:
你主动向解析器请求:“给我下一个事件!”
类比:
SAX 就像电视直播:事件“源源不断”地推给你,你需要被动应对。
StAX 则像点播服务:当你准备好时自己按下“下一个视频”。
StAX 的关键类
使用 StAX 需要包 javax.xml.stream 中的两个核心类:
- XMLInputFactory —— 用于创建解析器的工厂。
- XMLStreamReader —— 流式解析器,本质上“按块”读取 XML。
基础代码示例:
import javax.xml.stream.*;
import java.io.FileInputStream;
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (reader.hasNext()) {
int event = reader.next();
// 处理事件
}
reader.close();
2. StAX 的工作原理:pull 模型
在 StAX 中,你自行驱动 XML 的读取:
- 打开输入流(例如文件)。
- 创建 XMLStreamReader。
- 在循环中调用 reader.next() 获取下一个事件。
- 检查事件类型(START_ELEMENT、END_ELEMENT、CHARACTERS 等)。
- 到达目标位置时——处理数据。
- 关闭解析器。
工作流程示意:
flowchart TD
A[打开 XMLStreamReader] --> B{hasNext?}
B -- 是 --> C["next()"]
C --> D{事件类型?}
D -- START_ELEMENT --> E[处理元素开始]
D -- CHARACTERS --> F[处理文本]
D -- END_ELEMENT --> G[处理元素结束]
D -- END_DOCUMENT --> H[结束]
B -- 否 --> H
有什么好处?
- 你自己决定何时读取下一个元素。
- 可在需要的位置“暂停”,仅处理文件的一部分。
- 无需像 SAX 那样编写大量处理器。
3. StAX 中的事件类型
当你调用 reader.next() 时,解析器会返回一个事件类型——一个整数(来自接口 XMLStreamConstants 的常量)。主要事件类型有:
- START_ELEMENT —— XML 元素的开始(<tag>)。
- END_ELEMENT —— XML 元素的结束(</tag>)。
- CHARACTERS —— 标签之间的文本内容。
- END_DOCUMENT —— 文档结束。
事件处理示例:
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String name = reader.getLocalName();
System.out.println("元素开始: " + name);
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) {
System.out.println("文本: " + text);
}
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("元素结束: " + reader.getLocalName());
break;
}
}
4. 实用细节
何时使用 StAX?
在以下场景中,StAX 是理想选择:
- XML 文件非常大(以 GB 计),且不希望将其全部载入内存。
- 只需处理文档的一部分(例如找到某个特定元素后即可停止)。
- 需要简洁易懂的代码:StAX 比 SAX 更简单,不必编写大量处理器。
示例场景:
- 导入大型 XML 数据文件(例如来自 1C:Enterprise 的导出、银行对账单、商品目录)。
- 仅搜索并处理所需的元素(例如在百万条记录中只处理 <transaction>)。
- 对 XML 进行“边读边处理”(例如过滤、聚合)。
DOM、SAX 与 StAX 的对比
| 方法 | 内存 | 易用性 | 灵活性 | 使用场景 |
|---|---|---|---|---|
| DOM | 高(全部在内存中) | 非常简单 | 可修改树 | 小/中等文件,需要编辑 XML 时 |
| SAX | 最小 | 较复杂(事件处理器) | 只读,无法回退 | 超大文件,简单处理 |
| StAX | 最小 | 中等(pull 模型) | 可分段读取,易于暂停 | 大文件,需要灵活性与简单性 |
StAX —— 折中之选:
— 不像 DOM 那样耗内存。
— 不像 SAX 那样需要复杂的处理器。
— 允许你掌控解析过程。
5. 示例:用 StAX 读取大型 XML 文件
假设我们有文件 "books.xml":
<library>
<book>
<title>Java 入门</title>
<author>John Doe</author>
</book>
<book>
<title>高级 Java</title>
<author>Jane Smith</author>
</book>
<!-- ... 很多书 ... -->
</library>
任务:输出所有书名。
StAX 代码:
import javax.xml.stream.*;
import java.io.*;
public class StaxDemo {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("books.xml"));
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT && "title".equals(reader.getLocalName())) {
reader.next(); // 前进到 CHARACTERS
System.out.println("书籍: " + reader.getText());
}
}
reader.close();
}
}
优点:
- 无需将整个文件加载到内存。
- 即使有上百万本书,程序也不会“crash”。
6. 使用 StAX 的常见错误
错误 1:忘记关闭解析器或流。 始终关闭 XMLStreamReader 和输入流(InputStream),以避免资源泄漏。
错误 2:没有检查事件类型。 并非所有事件都是元素开始或结束。如果不检查事件类型,可能会得到空字符串或错过需要的数据。
错误 3:没有考虑元素的嵌套层级。 如果 XML 结构较复杂(例如书籍位于分区内),要跟踪当前的嵌套层级,避免混淆元素。
错误 4:对大文件仍使用 DOM。 如果文件很大,不要使用 DOM,否则可能得到 OutOfMemoryError。处理大文件应使用 StAX 或 SAX。
错误 5:没有处理异常。 文件与 XML 相关操作可能抛出异常(XMLStreamException、IOException)。要么捕获处理,要么向上抛出。
GO TO FULL VERSION