1. 简介
本文将介绍如何使用 StAX(Streaming API for XML)解析 XML 文件。我们会实现一个简单的 XML 解析器,并通过实际示例演示其工作原理。
对于 Java 开发者来说,XML 处理是常见需求,而 StAX 是性能和易用性之间的一个优秀平衡点。相比 DOM 一次性加载整个文档到内存,StAX 采用“拉模式”逐个读取事件,内存占用低,适合处理大文件。
2. 使用 StAX 进行解析
StAX 是 Java 内置的 XML 处理方式之一 ✅,自 Java 6 起就已包含在 JDK 中,无需引入第三方依赖。它属于流式解析器,不会将整个 XML 文档加载进内存,而是像“流水线”一样,按顺序一个事件一个事件地处理。
这种“前向只读”的特性让它在处理大型 XML 文件时非常高效,尤其适合日志解析、数据导入等场景。核心接口是 XMLEventReader
,它从输入流中读取一个个 XML 事件(如开始标签、结束标签、文本内容等)。
⚠️ 注意:StAX 是“拉”模型(pull),区别于 SAX 的“推”模型(push)。这意味着我们主动调用
nextEvent()
获取下一个事件,控制权在我们手上,代码逻辑更清晰。
3. XMLEventReader 类详解
在 StAX 中,每一个 XML 标签或文本内容都被视为一个 事件(Event)。XMLEventReader
就是用来遍历这些事件的核心类。
常用方法如下:
- ✅
isStartElement()
:判断当前事件是否为开始标签(如<website>
) - ✅
isEndElement()
:判断当前事件是否为结束标签(如</website>
) - ✅
asCharacters()
:将当前事件转为字符内容(即标签之间的文本) - ✅
getName()
:获取当前事件的标签名 - ✅
getAttributes()
:返回当前开始标签的属性集合(Iterator)
这些方法组合起来,就能精准地提取出我们需要的数据。
4. 实现一个简单的 XML 解析器
要开始解析,首先需要创建 XMLEventReader
。这需要一个工厂类 XMLInputFactory
:
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = xmlInputFactory.createXMLEventReader(new FileInputStream("websites.xml"));
接着,通过循环读取事件流:
while (reader.hasNext()) {
XMLEvent nextEvent = reader.nextEvent();
}
关键逻辑在于识别开始标签。例如,我们想捕获 <desired>
标签:
if (nextEvent.isStartElement()) {
StartElement startElement = nextEvent.asStartElement();
if (startElement.getName().getLocalPart().equals("desired")) {
// 找到目标标签,开始处理
}
}
获取标签属性也很简单:
String url = startElement.getAttributeByName(new QName("url")).getValue();
读取标签内的文本内容需要注意:文本本身也是一个事件,通常紧跟在开始标签之后:
nextEvent = reader.nextEvent(); // 移动到下一个事件(通常是文本)
String name = nextEvent.asCharacters().getData();
最后,通过判断结束标签来确认一个完整结构的结束:
if (nextEvent.isEndElement()) {
EndElement endElement = nextEvent.asEndElement();
if (endElement.getName().getLocalPart().equals("website")) {
// 一个 website 节点解析完成
websites.add(website);
}
}
5. 完整解析示例
假设我们有如下 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<websites>
<website url="https://baeldung.com">
<name>Baeldung</name>
<category>Online Courses</category>
<status>Online</status>
</website>
<website url="http://example.com">
<name>Example</name>
<category>Examples</category>
<status>Offline</status>
</website>
<website url="http://localhost:8080">
<name>Localhost</name>
<category>Tests</category>
<status>Offline</status>
</website>
</websites>
我们的目标是将其解析为 List<WebSite>
。完整代码如下:
List<WebSite> websites = new ArrayList<>();
WebSite website = null;
while (reader.hasNext()) {
XMLEvent nextEvent = reader.nextEvent();
if (nextEvent.isStartElement()) {
StartElement startElement = nextEvent.asStartElement();
switch (startElement.getName().getLocalPart()) {
case "website":
website = new WebSite();
Attribute url = startElement.getAttributeByName(new QName("url"));
if (url != null) {
website.setUrl(url.getValue());
}
break;
case "name":
nextEvent = reader.nextEvent();
website.setName(nextEvent.asCharacters().getData());
break;
case "category":
nextEvent = reader.nextEvent();
website.setCategory(nextEvent.asCharacters().getData());
break;
case "status":
nextEvent = reader.nextEvent();
website.setStatus(nextEvent.asCharacters().getData());
break;
}
}
if (nextEvent.isEndElement()) {
EndElement endElement = nextEvent.asEndElement();
if (endElement.getName().getLocalPart().equals("website")) {
websites.add(website);
}
}
}
关键点说明:
- ✅
getName().getLocalPart()
获取的是本地标签名,不带命名空间,推荐使用 - ✅ 属性通过
getAttributeByName(QName)
获取,注意传入的是QName
对象 - ✅ 文本内容必须调用
nextEvent()
获取下一个事件,不能直接从StartElement
中读取 - ✅ 在
</website>
结束时才将对象加入列表,确保数据完整
这个写法简单粗暴但非常有效,适合大多数场景。如果 XML 结构复杂,建议结合状态机或递归处理,避免嵌套过深。
6. 总结
本文演示了如何使用 JDK 自带的 StAX API 高效解析 XML 文件。相比 DOM 和 SAX,StAX 在内存占用和编码友好性之间取得了良好平衡。
✅ 优势:
- 内存友好,适合大文件
- 拉模型,逻辑清晰,易于控制
- JDK 原生支持,无需额外依赖
❌ 注意:
- 只支持前向遍历,不能回退
- 需手动管理事件顺序,容易踩坑(比如忘了调
nextEvent()
)
在实际项目中,如果你需要解析 GB 级别的 XML 日志或数据导出文件,StAX 是一个值得优先考虑的选择。