0%

SAX解析

SAX 解析:基于事件驱动的 XML 解析方式

SAX(Simple API for XML)是一种基于事件驱动的 XML 解析标准,与 DOM 解析的树形结构不同,SAX 无需将整个 XML 文档加载到内存,而是通过逐行扫描文档并触发事件的方式处理数据。这种特性使其在处理大型 XML 文档时具有显著的内存优势,成为 DOM 解析的重要补充。

SAX 解析的核心原理

SAX 解析的核心思想是 “流式处理 + 事件驱动”,其工作流程如下:

  1. 逐行扫描:SAX 解析器按顺序读取 XML 文档,无需一次性加载整个文档到内存。
  2. 事件触发:当解析到特定结构(如元素开始、文本内容、元素结束等)时,解析器触发对应的事件(如startElementcharactersendElement)。
  3. 事件处理:开发者通过自定义事件处理器(继承DefaultHandler),重写事件方法以响应特定事件,提取所需数据。
  4. 按需停止:解析过程中可随时终止(如找到目标数据后),无需解析剩余内容,节省资源。

SAX 解析的核心组件

SAX 解析主要依赖以下组件完成解析流程:

组件 作用
SAXParserFactory SAX 解析器的工厂类,用于创建SAXParser实例
SAXParser SAX 解析器的核心接口,负责读取 XML 并触发事件
DefaultHandler 事件处理器的基类,实现了 SAX 的四大事件监听接口,提供空方法实现
事件监听接口 包括ContentHandlerDTDHandlerEntityResolverErrorHandler,定义了各类事件的回调方法

四大事件监听接口

SAX 通过四大接口定义不同类型的事件,DefaultHandler实现了这些接口并提供空方法,开发者可按需重写:

(1)ContentHandler(核心接口,处理文档内容)

定义与 XML 元素、文本相关的事件,是最常用的接口:

  • startDocument():文档解析开始时触发。
  • endDocument():文档解析结束时触发。
  • startElement(String uri, String localName, String qName, Attributes atts):元素开始时触发(如<book>)。
    • uri:命名空间 URI(无则为空)。
    • localName:不带前缀的元素名。
    • qName:带前缀的元素名(如ns:book)。
    • atts:元素的属性集合。
  • endElement(String uri, String localName, String qName):元素结束时触发(如</book>)。
  • characters(char[] ch, int start, int length):解析到文本内容时触发,ch为字符数组,startlength指定文本范围。
  • startPrefixMapping(String prefix, String uri):命名空间前缀映射开始时触发。
  • endPrefixMapping(String prefix):命名空间前缀映射结束时触发。
(2)DTDHandler(处理 DTD 相关事件)
  • notationDecl(String name, String publicId, String systemId):解析 DTD 符号声明时触发。
  • unparsedEntityDecl(String name, String publicId, String systemId, String notationName):解析未解析实体时触发。
(3)EntityResolver(处理实体解析)
  • resolveEntity(String publicId, String systemId):解析外部实体(如 DTD 文件)时触发,可自定义实体加载方式(如本地缓存)。
(4)ErrorHandler(处理解析错误)
  • warning(SAXParseException e):非致命错误(如语法警告)。
  • error(SAXParseException e):可恢复的错误(如未闭合标签)。
  • fatalError(SAXParseException e):致命错误(如文档格式错误),会终止解析。

SAX 解析实战示例

以解析 MyBatis 的mapper.xml为例,演示 SAX 解析的核心操作:

示例 XML 文档(UserMapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<mapper namespace="com.example.UserMapper">
<select id="getUser" resultMap="userMap">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insertUser">
INSERT INTO user (name) VALUES (#{name})
</insert>
</mapper>

自定义事件处理器

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
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

// 继承DefaultHandler,重写需要的事件方法
public class MapperHandler extends DefaultHandler {
private String currentElement; // 当前处理的元素名
private String currentId; // 存储元素的id属性值

// 文档解析开始
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析文档...");
}

// 元素开始时触发
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentElement = qName; // 记录当前元素名

// 处理<mapper>元素的namespace属性
if ("mapper".equals(qName)) {
String namespace = attributes.getValue("namespace");
System.out.println("namespace: " + namespace); // 输出:com.example.UserMapper
}

// 处理<select>和<insert>元素的id属性
if ("select".equals(qName) || "insert".equals(qName)) {
currentId = attributes.getValue("id");
System.out.println("找到" + qName + "元素,id: " + currentId);
}
}

// 文本内容处理
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 提取<select>和<insert>中的SQL语句
if ("select".equals(currentElement) || "insert".equals(currentElement)) {
String sql = new String(ch, start, length).trim(); // 去除空白
if (!sql.isEmpty()) {
System.out.println(currentId + "的SQL: " + sql);
}
}
}

// 元素结束时触发
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
currentElement = null; // 重置当前元素名
}

// 文档解析结束
@Override
public void endDocument() throws SAXException {
System.out.println("文档解析结束。");
}
}

执行 SAX 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.InputStream;

public class SAXParserExample {
public static void main(String[] args) {
try {
// 1. 创建SAX解析器工厂和解析器
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();

// 2. 加载XML文件(通过类加载器获取资源流)
InputStream stream = ClassLoader.getSystemResourceAsStream("UserMapper.xml");

// 3. 解析XML,使用自定义处理器
parser.parse(stream, new MapperHandler());
} catch (Exception e) {
e.printStackTrace();
}
}
}

输出结果

1
2
3
4
5
6
7
开始解析文档...
namespace: com.example.UserMapper
找到select元素,id: getUser
getUser的SQL: SELECT * FROM user WHERE id = #{id}
找到insert元素,id: insertUser
insertUser的SQL: INSERT INTO user (name) VALUES (#{name})
文档解析结束。

SAX 解析的优缺点

优点

  1. 内存效率高:无需加载整个文档到内存,仅在解析时临时存储少量数据,适合处理 GB 级大型 XML。
  2. 解析速度快:流式处理无需构建树形结构,启动速度快,适合只需读取数据的场景。
  3. 可中断性:解析过程中可随时终止(如throw new SAXException()),避免无效解析。
  4. 低资源占用:对内存和硬件配置要求低,适合嵌入式设备或资源受限的环境。

缺点

  1. 无法随机访问:流式处理只能单向前进,无法回溯已解析的节点,不支持随机查询。
  2. 状态管理复杂:需要手动维护节点间的层级关系(如嵌套元素),代码复杂度高于 DOM。
  3. 不支持写入:SAX 仅用于读取 XML,无法修改或生成 XML 文档。
  4. 事件驱动门槛:需理解事件触发机制,初学者可能难以掌握。

DOM 与 SAX 的对比与选择

维度 DOM 解析 SAX 解析
核心思想 树形结构,加载整个文档到内存 事件驱动,流式处理,不存储文档结构
内存占用 高(与文档大小成正比) 低(恒定,与文档大小无关)
速度 慢(需构建完整树) 快(逐行解析)
随机访问 支持(可任意跳转节点) 不支持(只能单向前进)
读写支持 支持读取和修改 仅支持读取
适用场景 中小型文档、需修改或随机访问 大型文档、仅需读取数据、资源受限环境

选择建议

  • 中小型 XML、需修改结构或随机访问 → 选 DOM。
  • 大型 XML、仅需读取数据、内存有限 → 选 SAX。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10