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 是一个值得优先考虑的选择。


原始标题:Parsing an XML File Using StAX | Baeldung