抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文介绍了如何使用StAX解析XML文档。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8

1 定义

StAX(Streaming API for XML)是一种基于流式处理的XML解析API。StAX采用拉模式解析数据,允许应用程序主动控制解析过程,在需要时获取下一个解析事件。

2 基本原理

StAX解析器的核心工作流程:

  1. 创建解析器:通过XMLInputFactory工厂创建XMLStreamReader实例或XMLEventReader实例。
  2. 主动拉取:应用程序主动调用获取下一个解析事件。
  3. 处理事件:根据事件类型进行相应的数据处理。
  4. 状态控制:应用程序完全控制解析进度,可以随时停止或跳过内容。

关键特性:

  • 拉模式解析:应用程序控制解析流程。
  • 双向访问:支持读取和写入。
  • 内存高效:不需要将整个文档加载到内存。
  • 灵活性高:可以随时停止或跳过内容。

StAX提供两种编程模型:

  • 基于指针的模型(XMLStreamReader)通过next()方法获取事件,进而获取事件类型和具体内容,性能更高,使用更底层。
  • 基于迭代器的模型(XMLEventReader)通过nextEvent()方法获取事件对象,进而获取事件信息,更面向对象,使用更简单。

3 文档示例

以一个简单的XML文档为例:

school.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<school address="北京市海淀区">
<name>阳光小学</name>
<teachers>
<teacher id="teacher_1">
<name>李明</name>
<subject>语文</subject>
</teacher>
<teacher id="teacher_2">
<name>赵强</name>
<subject>数学</subject>
</teacher>
</teachers>
<students>
<student id="student_1">
<name>张婷</name>
<gender></gender>
<age>13</age>
<hobbies>
<hobby>画画</hobby>
<hobby>弹琴</hobby>
</hobbies>
</student>
<student id="student_2">
<name>王浩</name>
<gender></gender>
<age>14</age>
<hobbies>
<hobby>跑步</hobby>
<hobby>游泳</hobby>
</hobbies>
</student>
</students>
</school>

4 核心组件

4.1 工厂类

4.1.1 XMLInputFactory

常用方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
// 获取工厂实例
public static XMLInputFactory newInstance();
// 创建XMLStreamReader实例
public abstract XMLStreamReader createXMLStreamReader(InputStream stream);
public abstract XMLStreamReader createXMLStreamReader(Reader reader);
// 创建XMLEventReader实例
public abstract XMLEventReader createXMLEventReader(InputStream stream);
public abstract XMLEventReader createXMLEventReader(Reader reader);
// 获取属性
public abstract Object getProperty(String name);
// 设置属性
public abstract void setProperty(String name, Object value);

4.1.2 XMLOutputFactory

常用方法:

java
1
2
3
4
5
// 获取工厂实例
public static XMLOutputFactory newInstance();
// 创建XMLStreamWriter实例
public XMLStreamWriter createXMLStreamWriter(OutputStream stream);
public XMLStreamWriter createXMLStreamWriter(Writer writer);

4.2 读取器类

4.2.1 XMLStreamReader

常用方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 移动到下一个事件
public int next();
// 判断是否还有事件
public boolean hasNext();
// 获取当前事件类型
public int getEventType();
// 获取元素限定名
public QName getName();
// 获取元素本地名
public String getLocalName();
// 获取元素文本内容
public String getText();
// 获取属性数量
public int getAttributeCount();
// 获取属性限定名
public QName getAttributeName(int index);
// 获取属性本地名
public String getAttributeLocalName(int index);
// 获取属性值
public String getAttributeValue(int index);
// 获取命名空间数量
public int getNamespaceCount();
// 获取命名空间URI
public String getNamespaceURI(int index);

事件类型常量:

java
1
2
3
4
5
6
7
8
9
10
// 开始元素
XMLStreamReader.START_ELEMENT
// 结束元素
XMLStreamReader.END_ELEMENT
// 字符数据
XMLStreamReader.CHARACTERS
// 开始文档
XMLStreamReader.START_DOCUMENT
// 结束文档
XMLStreamReader.END_DOCUMENT

4.2.2 XMLEventReader

常用方法:

java
1
2
3
4
5
6
// 获取下一个事件
public XMLEvent nextEvent();
// 判断是否还有事件
public boolean hasNext();
// 预览下一个事件但不移动指针
public XMLEvent peek();

4.2.3 XMLEvent

常用方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
// 获取事件类型
public int getEventType();
// 是否开始处理元素事件
public boolean isStartElement();
// 是否结束处理元素事件
public boolean isEndElement();
// 是否处理文本事件
public boolean isCharacters();
// 是否开始处理文档事件
public boolean isStartDocument();
// 是否结束处理文档事件
public boolean isEndDocument();

4.3 写入器类

4.3.1 XMLStreamWriter

常用方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 写入文档开始
public void writeStartDocument();
// 写入文档结束
public void writeEndDocument();
// 写入开始元素
public void writeStartElement(String localName);
// 写入结束元素
public void writeEndElement();
// 写入空元素
public void writeEmptyElement(String localName);
// 写入文本
public void writeCharacters(String text);
// 写入属性
public void writeAttribute(String localName, String value);
// 写入命名空间
public void writeNamespace(String prefix, String namespaceURI);

5 实际应用

5.1 基础使用

5.1.1 使用XMLStreamReader解析

创建解析器并解析文档:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public List<Student> parseWithStreamReader(String filePath) {
List<Student> students = new ArrayList<>();
XMLStreamReader reader = null;
try {
// 创建XMLInputFactory对象
XMLInputFactory factory = XMLInputFactory.newInstance();
// 创建XMLStreamReader对象
reader = factory.createXMLStreamReader(new FileInputStream(filePath));
// 记录当前元素和文本
Student currentStudent = null;
String currentElement = null;
StringBuilder currentText = new StringBuilder();
// 处理文档开始事件
if (reader.getEventType() == XMLStreamReader.START_DOCUMENT) {
System.out.println("解析文档开始");
}
// 遍历读取事件
while (reader.hasNext()) {
int eventType = reader.next();
switch (eventType) {
// 处理元素开始事件
case XMLStreamReader.START_ELEMENT:
currentElement = reader.getLocalName();
if ("student".equals(currentElement)) {
currentStudent = new Student();
// 处理属性
String id = reader.getAttributeValue(null, "id");
currentStudent.setId(id);
}
break;
// 处理文本事件
case XMLStreamReader.CHARACTERS:
if (!reader.isWhiteSpace()) {
currentText.append(reader.getText());
}
break;
// 处理元素结束事件
case XMLStreamReader.END_ELEMENT:
String elementName = reader.getLocalName();
String text = currentText.toString().trim();
if (currentStudent != null) {
switch (elementName) {
case "name":
currentStudent.setName(text);
break;
case "gender":
currentStudent.setGender(text);
break;
case "age":
if (!text.isEmpty()) {
currentStudent.setAge(Integer.parseInt(text));
}
break;
case "hobby":
currentStudent.addHobby(text);
break;
case "student":
students.add(currentStudent);
currentStudent = null;
break;
}
}
currentText.setLength(0);
break;
}
}
// 处理文档结束事件
if (reader.getEventType() == XMLStreamReader.END_DOCUMENT) {
System.out.println("解析文档结束");
}
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyList();
} finally {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
return students;
}

5.1.2 使用XMLEventReader解析

创建解析器并解析文档:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public List<Student> parseWithEventReader(String filePath) {
List<Student> students = new ArrayList<>();
XMLEventReader reader = null;
try {
// 创建XMLInputFactory对象
XMLInputFactory factory = XMLInputFactory.newInstance();
// 创建XMLEventReader对象
reader = factory.createXMLEventReader(new FileInputStream(filePath));
// 记录当前元素和文本
Student currentStudent = null;
String currentElement = null;
// 遍历读取事件
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
// 处理文档开始事件
if (event.isStartDocument()) {
System.out.println("解析文档开始");
}
// 处理元素开始事件
if (event.isStartElement()) {
StartElement startElement = event.asStartElement();
currentElement = startElement.getName().getLocalPart();
if ("student".equals(currentElement)) {
currentStudent = new Student();
// 处理属性
Attribute idAttr = startElement.getAttributeByName(new QName("id"));
if (idAttr != null) {
currentStudent.setId(idAttr.getValue());
}
}
}
// 处理文本事件
if (event.isCharacters()) {
Characters characters = event.asCharacters();
String text = characters.getData().trim();
if (!text.isEmpty() && currentStudent != null) {
switch (currentElement) {
case "name":
currentStudent.setName(text);
break;
case "gender":
currentStudent.setGender(text);
break;
case "age":
currentStudent.setAge(Integer.parseInt(text));
break;
case "hobby":
currentStudent.addHobby(text);
break;
}
}
}
// 处理元素结束事件
if (event.isEndElement()) {
EndElement endElement = event.asEndElement();
String elementName = endElement.getName().getLocalPart();
if ("student".equals(elementName)) {
students.add(currentStudent);
currentStudent = null;
}
currentElement = null;
}
// 处理文档结束事件
if (event.isEndDocument()) {
System.out.println("解析文档结束");
}
}
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyList();
} finally {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
return students;
}

5.1.3 使用XMLStreamWriter写入

写入文档:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public void writeWithStreamWriter(List<Student> students, String filePath) {
XMLStreamWriter writer = null;
try {
// 创建XMLOutputFactory对象
XMLOutputFactory factory = XMLOutputFactory.newInstance();
// 创建XMLStreamWriter对象
writer = factory.createXMLStreamWriter(new FileOutputStream(filePath), "UTF-8");
// 写入声明开始
writer.writeStartDocument("UTF-8", "1.0");
writer.writeCharacters("\n");
// 写入学校开始
writer.writeStartElement("school");
writer.writeAttribute("address", "北京市海淀区");
writer.writeCharacters("\n ");
// 写入学校名称
writer.writeStartElement("name");
writer.writeCharacters("阳光小学");
writer.writeEndElement();
writer.writeCharacters("\n ");
// 写入学生列表开始
writer.writeStartElement("students");
writer.writeCharacters("\n");
// 写入每个学生信息
for (Student student : students) {
// 写入学生开始
writer.writeCharacters(" ");
writer.writeStartElement("student");
writer.writeAttribute("id", student.getId());
writer.writeCharacters("\n");
// 写入学生信息
writeElement(writer, "name", student.getName(), " ");
writeElement(writer, "gender", student.getGender(), " ");
writeElement(writer, "age", String.valueOf(student.getAge()), " ");
// 写入学生爱好开始
writer.writeCharacters(" ");
writer.writeStartElement("hobbies");
writer.writeCharacters("\n");
for (String hobby : student.getHobbies()) {
// 写入学生爱好
writeElement(writer, "hobby", hobby, " ");
}
// 写入学生爱好结束
writer.writeCharacters(" ");
writer.writeEndElement();
writer.writeCharacters("\n");
// 写入学生结束
writer.writeCharacters(" ");
writer.writeEndElement();
writer.writeCharacters("\n");
}
// 写入学生列表结束
writer.writeCharacters(" ");
writer.writeEndElement();
writer.writeCharacters("\n");
// 写入学校结束
writer.writeEndElement();
// 写入声明结束
writer.writeEndDocument();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
}

创建元素:

java
1
2
3
4
5
6
7
private void writeElement(XMLStreamWriter writer, String elementName, String value, String indent) throws XMLStreamException {
writer.writeCharacters(indent);
writer.writeStartElement(elementName);
writer.writeCharacters(value);
writer.writeEndElement();
writer.writeCharacters("\n");
}

5.2 高级特性

5.2.1 流式处理文件

批量处理文件信息:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void processStudentList(String filePath) {
XMLStreamReader reader = null;
try {
// 创建XMLInputFactory对象
XMLInputFactory factory = XMLInputFactory.newInstance();
// 创建XMLStreamReader对象
reader = factory.createXMLStreamReader(new FileInputStream(filePath));
// 每处理100个学生输出一次进度
int studentCount = 0;
int batchSize = 100;
// 遍历读取事件
while (reader.hasNext()) {
int eventType = reader.next();
if (eventType == XMLStreamReader.START_ELEMENT) {
if ("student".equals(reader.getLocalName())) {
if (studentCount % batchSize == 0) {
System.out.println("已处理 " + studentCount + " 个学生");
// 执行批量保存等操作
}
}
}
}
System.out.println("总共处理 " + studentCount + " 个学生");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}

5.2.2 使用命名空间

使用命名空间:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void parseWithNamespaces(String filePath) {
XMLStreamReader reader = null;
try {
// 创建XMLInputFactory对象
XMLInputFactory factory = XMLInputFactory.newInstance();
// 设置命名空间支持
factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
// 创建XMLStreamReader对象
reader = factory.createXMLStreamReader(new FileInputStream(filePath));
// 遍历读取事件
while (reader.hasNext()) {
int eventType = reader.next();
if (eventType == XMLStreamReader.START_ELEMENT) {
String localName = reader.getLocalName();
String namespaceURI = reader.getNamespaceURI();
String prefix = reader.getPrefix();
System.out.println("元素: " + localName + ", 命名空间: " + namespaceURI + ", 前缀: " + prefix);
// 处理命名空间声明
int namespaceCount = reader.getNamespaceCount();
for (int i = 0; i < namespaceCount; i++) {
String nsPrefix = reader.getNamespacePrefix(i);
String nsURI = reader.getNamespaceURI(i);
System.out.println("命名空间声明: " + nsPrefix + "=" + nsURI);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
}

6 对比

三种解析方式对比:

特性 DOM SAX StAX
解析模式 树形结构 事件驱动(推模式) 流式处理(拉模式)
内存使用 高(整个文档加载到内存) 低(不需要存储整个文档) 低(不需要存储整个文档)
性能 初始解析慢,但后续访问快 解析快,适合大型文件 解析快,性能接近SAX
访问方式 随机访问 顺序访问(只能向前) 顺序访问(可控制流程)
控制权 应用程序 解析器 应用程序
写入支持 完整支持 只读,写入困难 支持读写
易用性 简单直观 复杂,需要状态管理 中等,比SAX简单
适用场景 小型文档、需要修改 大型文档、只读操作 大型文档、需要控制流程

评论