DOM解析

DOM 解析:XML 文档的树形结构解析方式

DOM(Document Object Model,文档对象模型)是一种基于树形结构的 XML 解析标准,它将整个 XML 文档加载到内存中并构建一个完整的节点树,允许开发者通过树结构随机访问、修改和操作 XML 中的任何节点。这种解析方式直观且功能强大,是 XML 处理的重要手段之一。

DOM 解析的核心原理

DOM 解析的核心思想是将 XML 文档映射为一棵节点树,树中的每个节点对应 XML 文档的一个组成部分(如元素、属性、文本等)。开发者可以通过操作这棵树来访问和修改 XML 内容,具体流程如下:

  1. 加载文档:DOM 解析器读取整个 XML 文档,将其加载到内存中。
  2. 构建树结构:根据 XML 的层级关系,在内存中构建一棵 DOM 树,根节点为整个文档,各级子节点对应 XML 的元素、属性等。
  3. 操作节点:通过 DOM 提供的 API 遍历、查询、添加、修改或删除树中的节点。
  4. 持久化:(可选)将修改后的 DOM 树重新写入文件或输出流。

DOM 核心接口与类

Java 中 DOM 解析主要通过javax.xml.parsers包中的接口实现,核心组件包括:

接口 / 类 作用
DocumentBuilderFactory DOM 解析器的工厂类,用于创建DocumentBuilder
DocumentBuilder DOM 解析器的核心接口,负责解析 XML 并生成Document对象
Document 代表整个 XML 文档,是 DOM 树的根节点,提供创建和查询节点的方法
Node 所有 DOM 节点的父接口,定义节点的通用方法(如appendChildgetNodeValue
NodeList 节点集合,用于存储多个节点(如元素的子节点)
Element 元素节点的接口,继承自Node,提供属性操作等方法
Attr 属性节点的接口,代表元素的属性

关键接口方法解析

1. Document接口(核心方法)
  • getElementsByTagName(String tagname):根据标签名获取所有匹配的元素节点集合(NodeList)。
  • createElement(String tagName):创建指定名称的元素节点。
  • createTextNode(String data):创建文本节点。
  • createAttribute(String name):创建属性节点。
2. Node接口(核心方法)
  • appendChild(Node newChild):向当前节点添加子节点。
  • getChildNodes():获取当前节点的所有子节点(NodeList)。
  • getAttributes():获取当前节点的所有属性(NamedNodeMap)。
  • getNodeName():获取节点名称(如元素名、属性名)。
  • getNodeValue():获取节点值(如文本内容、属性值)。
  • hasChildNodes():判断当前节点是否有子节点。
3. NodeList接口(核心方法)
  • getLength():获取节点集合的长度。
  • item(int index):根据索引获取指定节点(索引从 0 开始)。

DOM 解析实战示例

以 MyBatis 的mapper.xml为例,演示 DOM 解析的常用操作:

示例 XML 文档(UserMapper.xml

<?xml version="1.0"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.UserMapper">
  <resultMap type="com.example.User" id="userMap">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
  </resultMap>
  
  <select id="getUser" resultMap="userMap">
    SELECT * FROM user WHERE id = #{id}
  </select>
</mapper>

解析 XML 并读取节点内容

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

public class DOMParserExample {
    public static void main(String[] args) {
        try {
            // 1. 创建解析器工厂和解析器
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            
            // 2. 解析XML文件,生成Document对象(DOM树)
            Document document = builder.parse(new File("UserMapper.xml"));
            
            // 3. 读取根节点<mapper>的namespace属性
            NodeList mapperList = document.getElementsByTagName("mapper");
            Node mapperNode = mapperList.item(0); // 获取第一个mapper节点
            if (mapperNode.hasAttributes()) {
                Node namespaceNode = mapperNode.getAttributes().getNamedItem("namespace");
                System.out.println("namespace: " + namespaceNode.getNodeValue()); // 输出:com.example.UserMapper
            }
            
            // 4. 遍历<mapper>的子节点(resultMap、select等)
            NodeList childNodes = mapperNode.getChildNodes();
            for (int i = 0; i < childNodes.getLength(); i++) {
                Node child = childNodes.item(i);
                // 忽略空白节点(文本节点中的空格、换行)
                if (child.getNodeType() != Node.ELEMENT_NODE) continue;
                
                String nodeName = child.getNodeName();
                if ("resultMap".equals(nodeName)) {
                    // 读取resultMap的id属性
                    String id = child.getAttributes().getNamedItem("id").getNodeValue();
                    System.out.println("resultMap id: " + id); // 输出:userMap
                } else if ("select".equals(nodeName)) {
                    // 读取select的id和文本内容
                    String id = child.getAttributes().getNamedItem("id").getNodeValue();
                    String sql = child.getTextContent().trim(); // 去除空白
                    System.out.println("select id: " + id); // 输出:getUser
                    System.out.println("SQL: " + sql); // 输出:SELECT * FROM user WHERE id = #{id}
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建和修改 DOM 节点

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;

public class DOMCreatorExample {
    public static void main(String[] args) {
        try {
            // 1. 创建空的Document对象
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.newDocument(); // 创建空白文档
            
            // 2. 创建根节点<book>并设置属性
            Element root = document.createElement("book");
            root.setAttribute("id", "1001"); // 设置属性
            document.appendChild(root); // 将根节点添加到文档
            
            // 3. 创建子节点<title>和<author>
            Element title = document.createElement("title");
            title.setTextContent("XML入门指南"); // 设置文本内容
            root.appendChild(title);
            
            Element author = document.createElement("author");
            author.setTextContent("张三");
            root.appendChild(author);
            
            // 4. 将DOM树写入文件(持久化)
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            // 设置XML版本和编码
            transformer.setOutputProperty("version", "1.0");
            transformer.setOutputProperty("encoding", "UTF-8");
            
            DOMSource source = new DOMSource(document);
            StreamResult result = new StreamResult(new File("new_book.xml"));
            transformer.transform(source, result); // 写入文件
            
            System.out.println("XML文件创建成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

生成的new_book.xml内容

<?xml version="1.0" encoding="UTF-8"?>
<book id="1001">
  <title>XML入门指南</title>
  <author>张三</author>
</book>

DOM 解析器的配置

DocumentBuilderFactory提供了多种配置方法,用于定制解析器行为:

方法 作用 默认值
setNamespaceAware(boolean aware) 是否支持 XML 命名空间 false
setValidating(boolean validating) 是否验证 XML 文档(根据 DTD/Schema) false
setIgnoringComments(boolean ignore) 是否忽略注释节点 false
setIgnoringElementContentWhitespace(boolean ignore) 是否忽略元素内容中的空白节点 false
setCoalescing(boolean coalescing) 是否将 CDATA 节点合并为文本节点 false

示例:配置支持命名空间并忽略注释

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // 支持命名空间
factory.setIgnoringComments(true); // 忽略注释
DocumentBuilder builder = factory.newDocumentBuilder();

DOM 解析的优缺点

优点

  1. 随机访问:DOM 树在内存中完整存在,可通过节点间的关系(父子、兄弟)随机访问任何节点,灵活性高。
  2. 双向操作:不仅可以读取 XML,还能方便地添加、修改、删除节点,适合需要修改 XML 结构的场景。
  3. 直观易用:树形结构与 XML 的层级关系一致,符合人类对文档结构的理解。

缺点

  1. 内存消耗大:需要将整个 XML 文档加载到内存并构建 DOM 树,对于大型 XML(如几百 MB)可能导致内存溢出。
  2. 解析速度慢:加载和构建大型 DOM 树需要更多时间,效率低于流式解析(如 SAX)。