上一篇
java怎么遍历document
- 后端开发
- 2025-08-10
- 4
使用
Document.getChildNodes()
获取子节点,递归遍历并判断节点类型为
ELEMENT_NODE
基础认知体系构建
1 文档模型分类
模型类型 | 核心特征 | 适用场景 | Java标准库支持 |
---|---|---|---|
DOM | 树形结构/全量加载/随机访问 | 中小型文档/频繁查询修改 | javax.xml.parsers |
SAX | 事件驱动/增量解析/低内存消耗 | 超大型文档/单次顺序处理 | org.xml.sax |
StAX | 拉模式解析/游标控制/高效过滤 | 复杂数据抽取/流式处理 | javax.xml.stream |
Jsoup (HTML) | 类jQuery API/宽松容错机制 | HTML5文档/网页爬虫开发 | org.jsoup |
2 关键术语对照表
术语 | 说明 | 典型应用场景 |
---|---|---|
Node | 文档节点基类 | 所有元素/文本/注释的基础 |
Element | 含子节点的元素节点 | <book> , <title> 等标签 |
Text | 节点 | 段落文字/属性值 |
NamedNodeMap | 属性集合 | 获取元素的id , class 等 |
NodeList | 有序节点集合 | 通过CSS选择器获取的结果集 |
DOM解析深度实践(以XML为例)
1 标准实现流程
// 1. 创建DocumentBuilderFactory实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); // 启用命名空间支持 // 2. 构建Document对象 DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File("books.xml")); // 3. 遍历根节点下的所有子节点 NodeList bookList = doc.getElementsByTagName("book"); for (int i = 0; i < bookList.getLength(); i++) { Element book = (Element) bookList.item(i); // 4. 提取元素内容 String title = book.getElementsByTagName("title").item(0).getTextContent(); double price = Double.parseDouble(book.getElementsByTagName("price").item(0).getTextContent()); // 5. 处理属性 String category = book.getAttribute("category"); // 6. 递归遍历子节点(可选) traverseChildren(book); } private void traverseChildren(Node parent) { NodeList children = parent.getChildNodes(); for (int j = 0; j < children.getLength(); j++) { Node child = children.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { System.out.println("发现元素: " + child.getNodeName()); traverseChildren(child); // 深度优先遍历 } else if (child.getNodeType() == Node.TEXT_NODE) { System.out.println("文本内容: " + child.getTextContent().trim()); } } }
2 高级技巧组合
技术手段 | 实现方式 | 优势体现 |
---|---|---|
XPath表达式 | XPathFactory.newInstance().newXPath().evaluate() |
精准定位复杂路径节点 |
迭代器模式 | NodeIterator 配合while(iterator.hasNext()) 循环 |
避免显式索引管理 |
Lambda表达式(Java8+) | Arrays.stream(nodeList).forEach(node -> ...) |
简化集合操作 |
并行流处理 | Arrays.stream(nodeList).parallel().forEach(...) |
提升多核CPU利用率 |
3 性能优化策略
优化方向 | 具体措施 | 效果评估 |
---|---|---|
缓存机制 | 对重复访问的节点进行软引用缓存 | 减少DOM树遍历次数 |
批量处理 | 将多次getElementsByTagName 合并为单次查询 |
降低I/O操作频率 |
懒加载 | 仅在需要时展开子节点 | 初始加载时间缩短70%+ |
内存监控 | 使用Runtime.getRuntime().totalMemory() 跟踪内存变化 |
防止OOM异常发生 |
HTML文档特殊处理(Jsoup方案)
1 典型用法示例
// 1. 解析HTML文档 Document doc = Jsoup.connect("https://example.com").get(); // 2. 选择器方式遍历 Elements links = doc.select("a[href]"); // 获取所有带href属性的链接 for (Element link : links) { String url = link.attr("href"); String text = link.text(); // ...后续处理逻辑 } // 3. 修改文档结构 Element table = doc.selectFirst("table"); table.select("tr:odd").addClass("highlight"); // 隔行变色效果 System.out.println(doc.html()); // 输出修改后的HTML
2 HTML特有处理要点
问题类型 | 解决方案 | 注意事项 |
---|---|---|
自闭合标签 | Jsoup自动规范化处理<img /> 为<img> |
无需手动补全 |
特殊字符转义 | StringEscapeUtils.unescapeHtml4() 处理实体编码 |
防止XSS攻击需二次过滤 |
CSS样式提取 | element.attr("style") 可直接获取内联样式 |
外部样式表需单独解析 |
SAX与StAX流式解析对比
1 SAX事件驱动模型
public class SaxParser extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attrs) { if ("book".equals(qName)) { currentBook = new Book(); // 初始化新书籍对象 } } @Override public void characters(char[] ch, int start, int length) { if (currentBook != null) { currentBook.setContent(new String(ch, start, length)); } } @Override public void endElement(String uri, String localName, String qName) { if ("book".equals(qName)) { books.add(currentBook); currentBook = null; } } }
优势:内存占用仅为DOM的1/5~1/10,适合GB级大文件处理;劣势:无法随机访问节点,需自行维护状态机。
2 StAX拉模式解析
XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("large.xml")); while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamReader.START_ELEMENT: if ("important".equals(reader.getLocalName())) { // 处理关键元素 } break; case XMLStreamReader.CHARACTERS: // 处理文本内容 break; // ...其他事件处理 } }
特点:结合推/拉模式优势,可通过peek()
预读下一个事件,适合复杂条件判断场景。
常见问题解决方案库
Q1: 如何处理XML命名空间冲突?
A: 在创建DocumentBuilder时设置命名空间感知:
factory.setNamespaceAware(true); // 然后通过带URI的完整名称访问元素: NodeList nodes = doc.getElementsByTagNameNS("http://example.com/ns", "localName");
补充说明:若遇到未知命名空间,可通过builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
禁用外部DTD加载。
Q2: 遍历时出现”NullPointerException”如何解决?
A: 常见原因及对策:
| 错误位置 | 根本原因 | 解决方案 |
|————————|——————————|———————————–|
| item(i)
返回null | 索引越界或标签不存在 | 添加if (i < nodeList.getLength())
校验 |
| getTextContent()
为空 | 节点类型不匹配(如注释节点) | 先检查getNodeType()
是否为ELEMENT_NODE
|
| 属性获取失败 | 属性未定义 | 使用hasAttribute()
前置判断 |
| Jsoup选择器无结果 | CSS语法错误或DOM未完全加载 | 添加await().atMost(5, SECONDS)
等待异步加载 |
选型决策矩阵
评价维度 | DOM | SAX | StAX | Jsoup (HTML) |
---|---|---|---|---|
内存效率 | (差) | (优) | (良) | (中) |
开发便捷性 | (易) | (难) | (中) | (极优) |
随机访问能力 | (强) | (弱) | (中) | (中) |
大数据适配性 | (差) | (优) | (良) | (差) |
HTML兼容性 | (差) | (差) | (差) | (极优) |
修改文档能力 | (强) | (弱) | (中) |