DOM 解析
本章定位 :掌握 DOM(Document Object Model)解析——树构建过程、核心 API(Document/Element/NodeList)、导航方法和增删改操作。
定义与作用
DOM (Document Object Model)是 W3C 标准,将 XML/HTML 文档表示为内存中的 节点树 。解析器读取整个 XML 文档,构建一棵完整的树,然后程序可以在树上执行任意操作——遍历、查询、修改、删除、新增。
DOM 是"重量级"方案:它的代价是内存(整棵树驻留内存),回报是最大的灵活性(任何节点随时访问,任何位置随时修改)。
核心原理:从 XML 到 DOM 树
图解释 :DOM 解析器将 XML 文本解析为节点树。每种 XML 结构对应一种节点类型(Document / Element / Text / Attr)。构建完成后,通过 API 进行导航、查找和修改。
语法/结构要点
核心接口
| 接口 | 作用 | 常用方法 |
|---|---|---|
Document | 整个文档的根 | getDocumentElement() createElement() getElementsByTagName() |
Element | 元素节点 | getAttribute() setAttribute() getElementsByTagName() appendChild() removeChild() |
Node | 所有节点基类 | getNodeName() getNodeValue() getChildNodes() getParentNode() |
NodeList | 节点集合 | item(index) getLength() |
Text | 文本节点 | getData() setData() |
导航方法
| 方法/属性 | 返回 | 说明 |
|---|---|---|
getChildNodes() | NodeList | 所有子节点(包括文本和注释) |
getFirstChild() | Node | 第一个子节点 |
getLastChild() | Node | 最后一个子节点 |
getParentNode() | Node | 父节点 |
getNextSibling() | Node | 下一个兄弟节点 |
getElementsByTagName(name) | NodeList | 按标签名查找(递归) |
完整示例:小崔用 DOM 管理学生信息
场景说明
飞翔科技的后端 小崔 负责学生管理系统。需求:①统计学生数量;②给所有 2023 级学生加一个 batch 属性;③删除 GPA 低于 2.0 的学生。
XML 数据
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student id="S001" enrollYear="2023">
<name>小崔</name>
<gpa>3.8</gpa>
</student>
<student id="S002" enrollYear="2023">
<name>阿呆</name>
<gpa>1.5</gpa>
</student>
<student id="S003" enrollYear="2024">
<name>黄俪</name>
<gpa>3.5</gpa>
</student>
</students>
Java DOM 操作
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
import java.io.File;
public class StudentManager {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("students.xml"));
// ① 统计学生数
NodeList students = doc.getElementsByTagName("student");
System.out.println("学生总数: " + students.getLength());
// ② 给 2023 级学生加 batch="2023A"
for (int i = 0; i < students.getLength(); i++) {
Element student = (Element) students.item(i);
if ("2023".equals(student.getAttribute("enrollYear"))) {
student.setAttribute("batch", "2023A");
}
}
// ③ 删除 GPA < 2.0 的学生
// 注意:不能在遍历 NodeList 时直接删除(会改变列表)
// 用父节点收集待删除列表
Element root = doc.getDocumentElement();
NodeList toDelete = doc.getElementsByTagName("gpa");
for (int i = 0; i < toDelete.getLength(); i++) {
Element gpaElem = (Element) toDelete.item(i);
double gpa = Double.parseDouble(gpaElem.getTextContent());
if (gpa < 2.0) {
Node studentNode = gpaElem.getParentNode();
root.removeChild(studentNode);
System.out.println("已删除低GPA学生");
}
}
// ④ 保存修改后的文档
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(
new DOMSource(doc),
new StreamResult(new File("students_updated.xml"))
);
System.out.println("修改已保存到 students_updated.xml");
}
}
操作结果
学生总数: 3
已删除低GPA学生
修改已保存到 students_updated.xml
修改后的 XML:
<students>
<student batch="2023A" enrollYear="2023" id="S001">
<name>小崔</name><gpa>3.8</gpa>
</student>
<student enrollYear="2024" id="S003">
<name>黄俪</name><gpa>3.5</gpa>
</student>
</students>
阿呆(GPA=1.5)被移除,2023 级的小崔多了 batch 属性。
易错场景
错误一:遍历时直接删除节点导致 IndexOutOfBounds
// ❌ NodeList 是动态的,删除会改变长度
for (int i = 0; i < students.getLength(); i++) {
root.removeChild(students.item(i));
}
正确做法:收集待删除节点,循环结束后再删除;或倒序遍历。
错误二:忘记保存修改
DOM 修改的是内存中的树,必须用 Transformer 写回文件,否则修改丢失。
面试考点
| 考点 | 参考答案要点 |
|---|---|
| DOM 解析的工作流程? | ①创建 DocumentBuilderFactory → ②创建 DocumentBuilder → ③parse() 构建内存中的 Document 树 → ④通过 DOM API 操作节点树 → ⑤Transformer 写回文件 |
| DOM 的优缺点? | 优点:支持随机访问和修改,API 直观。缺点:全文档驻留内存,大文档导致 OOM |
| getElementsByTagName 和 getChildNodes 的区别? | getElementsByTagName 递归搜索所有后代;getChildNodes 只返回直接子节点(包括空白文本节点) |