SAX全称为Simple API for XML,也就是XML的简单API,它是一种基于事件驱动的XML解析技术,和DOM解析需要将整个XML文档加载到内存构建树形结构不同,SAX采用流式读取的方式处理XML内容,在解析过程中会依次触发不同的事件,开发者只需要实现对应的事件处理逻辑即可完成XML内容的提取。

SAX的核心特点
SAX的解析逻辑和DOM有本质区别,它的核心特点可以总结为以下几点:
- 内存占用低:不需要加载整个XML文档,只会在解析过程中临时存储当前处理节点的信息,适合处理GB级别的超大XML文件。
- 只读特性:SAX只能读取XML内容,无法修改、新增或删除XML节点,若需要修改文档结构则需要结合其他技术实现。
- 顺序解析:严格按照XML文档的从前到后的顺序处理内容,无法随机访问某个特定节点,必须从头开始遍历到目标位置。
- 事件驱动:解析过程由一系列预定义的事件驱动,比如开始文档、结束文档、开始元素、结束元素、文本内容等。
SAX事件驱动模型的工作流程
SAX的事件驱动模型核心是解析器和事件处理器两部分,整个解析流程如下:
- 开发者创建自定义的事件处理器,继承SAX提供的默认处理器基类,重写需要处理的事件方法。
- 创建SAX解析器实例,将自定义的事件处理器注册到解析器中。
- 解析器读取XML文档,按照顺序扫描文档内容,每遇到一个符合预定义规则的节点,就触发对应的事件。
- 事件处理器接收到事件回调后,执行开发者编写的业务逻辑,比如提取元素内容、记录元素属性等。
- 文档解析完成后,触发结束文档事件,整个解析流程结束。
常用SAX事件类型说明
SAX定义了一系列标准事件,开发者可以根据需求选择重写对应的方法,常见的事件类型如下:
| 事件类型 | 触发时机 | 对应回调方法示例 |
|---|---|---|
| 开始文档 | XML文档解析开始时触发 | startDocument() |
| 结束文档 | XML文档解析完成时触发 | endDocument() |
| 开始元素 | 遇到XML元素的开始标签时触发 | startElement(uri, localName, qName, attributes) |
| 结束元素 | 遇到XML元素的结束标签时触发 | endElement(uri, localName, qName) |
| 字符数据 | 解析到元素内的文本内容时触发 | characters(ch, start, length) |
Java语言实现SAX解析示例
下面以Java语言为例,演示如何使用SAX解析一个存储用户信息的XML文件,示例的XML内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="1">
<name>张三</name>
<age>25</age>
<email>zhangsan@ipipp.com</email>
</user>
<user id="2">
<name>李四</name>
<age>28</age>
<email>lisi@ipipp.com</email>
</user>
</users>
首先创建自定义的事件处理器,继承DefaultHandler类,重写需要的事件方法:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
public class UserSAXHandler extends DefaultHandler {
// 存储解析得到的用户列表
private List<User> userList = new ArrayList<>();
// 当前正在解析的用户对象
private User currentUser;
// 记录当前解析的元素名称
private String currentElement;
// 获取解析完成的用户列表
public List<User> getUserList() {
return userList;
}
// 开始文档事件
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析XML文档");
}
// 结束文档事件
@Override
public void endDocument() throws SAXException {
System.out.println("XML文档解析完成");
}
// 开始元素事件
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentElement = qName;
if ("user".equals(qName)) {
// 遇到user标签,创建新的用户对象
currentUser = new User();
// 获取id属性
String id = attributes.getValue("id");
currentUser.setId(Integer.parseInt(id));
}
}
// 结束元素事件
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("user".equals(qName)) {
// user标签结束,将当前用户加入列表
userList.add(currentUser);
currentUser = null;
}
currentElement = null;
}
// 字符数据事件
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (currentElement == null || currentUser == null) {
return;
}
String content = new String(ch, start, length).trim();
if (content.length() == 0) {
return;
}
// 根据当前元素名称赋值对应的属性
if ("name".equals(currentElement)) {
currentUser.setName(content);
} else if ("age".equals(currentElement)) {
currentUser.setAge(Integer.parseInt(content));
} else if ("email".equals(currentElement)) {
currentUser.setEmail(content);
}
}
}
对应的User实体类代码如下:
public class User {
private int id;
private String name;
private int age;
private String email;
// getter和setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}
最后编写解析入口代码,创建SAX解析器并执行解析:
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
public class SAXParseDemo {
public static void main(String[] args) {
try {
// 创建SAX解析器工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
// 获取SAX解析器实例
SAXParser parser = factory.newSAXParser();
// 创建自定义事件处理器
UserSAXHandler handler = new UserSAXHandler();
// 执行解析,传入XML文件路径和事件处理器
parser.parse(new File("users.xml"), handler);
// 输出解析结果
for (User user : handler.getUserList()) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SAX的适用场景与局限性
SAX并不是所有XML解析场景的最优选择,开发者需要根据实际需求选择:
适用场景
- 需要解析体积非常大的XML文件,内存资源有限无法加载整个文档。
- 只需要读取XML内容,不需要修改文档结构,比如日志文件解析、数据导入等场景。
- 对解析速度要求较高,不需要随机访问文档中的特定节点。
局限性
- 无法修改XML文档,若需要增删改节点需要额外处理。
- 只能顺序访问,无法回退到之前的节点,若要重复处理内容需要重新解析。
- 需要开发者自行维护解析状态,代码逻辑相对DOM会更复杂一些。
总的来说,SAX是一种高效的XML解析方式,尤其适合处理大文件场景,理解其事件驱动的工作机制,能够帮助开发者在合适的场景中正确使用这项技术。