StAX 解析
本章定位 :掌握 StAX(Streaming API for XML)的拉式解析模型——XMLStreamReader 游标 API 和 XMLEventReader 迭代器 API,以及与 SAX 的差异。
定义与作用
StAX (Streaming API for XML)是 Java 6 引入的流式解析 API。与 SAX 的"推式"不同,StAX 是"拉式"——应用程序主动调用 next() 方法获取下一个事件。
这带来一个关键优势: 应用程序控制解析节奏 。可以随时停止、跳过不需要的部分、甚至在找到第一个匹配项后立即结束。
StAX 提供两种 API 风格:
- 游标 API (XMLStreamReader):更快,更像指针遍历
- 迭代器 API (XMLEventReader):更灵活,事件可缓存和回放
核心原理:SAX(推) vs StAX(拉)
图解释 :SAX 像电话推销(被动接听),解析器不停推送事件直到文档结束。StAX 像自助取餐(主动拿取),应用程序自己决定什么时候去拿下一个事件。
语法/结构要点
游标 API 关键方法
| 方法 | 返回 | 说明 |
|---|---|---|
next() | int(事件类型常量) | 前进到下一个事件 |
getEventType() | int | 当前事件类型 |
getLocalName() | String | 当前元素/属性的本地名 |
getAttributeValue(index) | String | 按索引取属性值 |
getElementText() | String | 获取纯文本元素内容 |
hasNext() | boolean | 是否还有事件 |
事件类型常量
| 常量 | 对应 XML 结构 |
|---|---|
START_ELEMENT | <tag> |
END_ELEMENT | </tag> |
CHARACTERS | 文本内容 |
START_DOCUMENT | 文档开头 |
END_DOCUMENT | 文档结尾 |
完整示例:黄俪用 StAX 找到即停
场景说明
飞翔科技的测试 黄俪 有一个 50MB 的测试报告 XML,只想找"第一个 status='fail' 的用例"后立即停止。
游标 API 实现
import javax.xml.stream.*;
import java.io.FileInputStream;
public class FirstFailureFinder {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(
new FileInputStream("test_report.xml"));
String currentElement = "";
boolean found = false;
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamReader.START_ELEMENT) {
currentElement = reader.getLocalName();
if ("case".equals(currentElement)) {
String status = reader.getAttributeValue(null, "status");
if ("fail".equals(status)) {
String id = reader.getAttributeValue(null, "id");
String name = reader.getElementText();
System.out.println("第一个失败用例: ["
+ id + "] " + name);
found = true;
break; // 找到即停!
}
}
}
}
if (!found) {
System.out.println("所有用例均通过!");
}
reader.close();
}
}
对比 SAX 实现
// SAX 做同样的事:必须解析整个文件(或手动抛异常)
class FindFirstSAX extends DefaultHandler {
boolean found = false;
public void startElement(String uri, String local,
String qName, Attributes atts) {
if (found) return; // 仍会触发后续回调
if ("case".equals(qName) && "fail".equals(atts.getValue("status"))) {
System.out.println("第一个失败用例: ["
+ atts.getValue("id") + "]");
found = true;
// SAX 没有优雅的"停止"机制……
}
}
}
操作结果
StAX 版本在找到第一个失败用例后立即 break 退出循环,剩下的 95% 文档完全不解析。而 SAX 版本即使用 found 标志跳过了处理逻辑,解析器仍然会继续扫描整个文件。
易错场景
错误一:getElementText 后当前位置变了
if ("title".equals(reader.getLocalName())) {
String text = reader.getElementText(); // 消费了文本 + END_ELEMENT
// 此时 reader 已经指向 title 之后的下一个事件
// 不要再调用 getLocalName() 期望它是 "title"
}
getElementText() 会持续读取直到 END_ELEMENT,调用后当前位置已前进。
错误二:忘记检查事件类型
while (reader.hasNext()) {
reader.next();
String name = reader.getLocalName();
// ❌ 如果是 CHARACTERS 事件,getLocalName() 抛异常
}
每次 next() 后应先检查 getEventType(),确认是 START_ELEMENT 再调用 getLocalName()。
面试考点
| 考点 | 参考答案要点 |
|---|---|
| StAX 的"拉式"模型与 SAX 的"推式"模型有何区别? | SAX 解析器主动推送事件(被动接收),无法暂停;StAX 应用主动拉取事件(调用 next()),可随时停止、跳过、控制节奏 |
| StAX 的两种 API 风格及选择? | 游标 API(XMLStreamReader)更快更省内存;迭代器 API(XMLEventReader)可缓存事件、支持回放。性能优先用游标,需要灵活性用迭代器 |
| 何时用 StAX 而非 SAX? | 需要按条件中途停止时;需要跳过大部分文档时;需要更好的代码可读性和可维护性(不用写复杂的回调状态机) |