必须重写startelement、endelement和characters方法;它们是sax解析xml结构、标签闭合和文本内容的事实入口,缺一不可。

ContentHandler 接口里哪些方法必须重写?
不重写 startElement 和 endElement,就拿不到标签结构;不重写 characters,文本内容全丢。SAX 不是“选配”,是“必填”——DefaultHandler 虽提供了空实现,但真解析 XML 时,这三个就是事实上的入口。
常见错误:只重写 startElement,以为能靠它“抓到所有信息”。结果发现 characters 返回的 char[] 带前后空白、换行,甚至跨多次回调(尤其大文本或含 CDATA),没做缓冲合并就直接 toString(),内容就断成几截。
-
characters的 char[] 是只读快照,不能存引用,得拷贝或用new String(ch, start, length) - 标签嵌套时,
startElement和endElement的调用顺序严格匹配,可借栈或 depth 计数跟踪上下文 - 如果需要属性值,从
Attributes参数里取,别去解析characters—— 属性不在那里
为什么 parse() 抛 SAXParseException 却没行号?
默认 XMLReader 不开启定位支持。即使 XML 文件本身有错,getLineNumber() 和 getColumnNumber() 也常返回 -1。
必须显式设置特性:setFeature("http://xml.org/sax/features/validation", false) 没问题,但关键在:setFeature("http://xml.org/sax/features/namespace-prefixes", true) 不影响定位;真正要开的是:setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false) —— 这个不相关。正解是确保底层解析器支持,并启用定位特性:
立即学习“Java免费学习笔记(深入)”;
- 用
SAXParserFactory.newInstance().newSAXParser()创建的 parser 默认支持定位 - 但若手动 new
XMLReader(比如XMLReaderFactory.createXMLReader()),需确认具体实现类(如org.apache.xerces.parsers.SAXParser)并调用setFeature("http://xml.org/sax/features/locator", true) - 更稳妥做法:始终通过
SAXParser获取XMLReader,它已预设好定位能力
自定义 ContentHandler 如何安全提取嵌套字段?
比如解析 <book><author><name>Zhang</name></author></book>,想拿到 author.name。不能靠字符串拼接标签名,得用状态机或路径栈。
容易踩的坑:用一个 String currentPath = "" 在 startElement 里拼接,遇到同名子标签(如多个 <item></item>)就串场;或者在 characters 里盲目赋值,结果把外层 <title></title> 的内容刷到了内层字段上。
- 推荐用
Deque<string></string>记录当前路径,startElementpush,endElementpop - 只在栈顶是
"author"且次顶是"book"时才处理后续name标签,避免误匹配 -
characters回调可能发生在任意标签内,务必检查当前栈状态再决定是否采集 - 别在
characters里直接 set 字段——先缓存 StringBuilder,等对应endElement触发后再赋值
和 DOM / StAX 比,SAX 在什么场景下真合适?
内存受限、流式处理、只读单遍扫描——这是 SAX 的真实地盘。不是“学了更高级”,而是“不用它就 OOM”。
比如解析 200MB 的日志 XML 流,DOM 会吃掉 1GB+ 堆内存;StAX 虽然也流式,但需要手动控制事件循环,写起来啰嗦。SAX 的 callback 模型反而更贴近“来了就处理”的直觉。
- 适合:ETL 清洗、配置批量校验、日志聚合、生成摘要(如统计某标签出现次数)
- 不适合:需要随机访问节点、反复修改结构、依赖 XPath 查询——这些时刻,SAX 就是自找麻烦
- 兼容性没问题:JDK 1.4+ 自带,无需额外依赖;但注意 Android 早期版本对某些 Xerces 特性支持不全
最易被忽略的一点:SAX 是单线程的,parse() 是阻塞调用,且 ContentHandler 实例不能复用——每次 parse 都该 new 一个新 handler,否则字段状态会串。










