XML 作為企業級應用中經久不衰的資料交換格式,Java 生態提供了多種解析技術。本文系統梳理 DOM、SAX、JDOM、Dom4j 四大主流解析方式的核心原理、適用場景、程式碼實戰與優劣對比,助你根據專案需求做出最佳技術選型。
| 維度 | DOM | SAX | JDOM | Dom4j |
|---|---|---|---|---|
| 解析模式 | 樹模型(一次性載入全文檔) | 事件驅動(流式單向讀取) | 樹模型(輕量級封裝) | 樹模型(高效能實作) |
| 記憶體佔用 | 高(正比文檔大小) | 極低(不載入樹) | 中等 | 中等偏低 |
| 隨機存取 | 支援完整 | 不支援(單向流) | 支援 | 支援 |
| 修改/寫入 | 支援 | 不支援(唯讀) | 支援 | 支援 |
| XPath 支援 | 需額外庫 | 不支援 | 有限支援 | 原生完整支援 |
| 學習曲線 | 平緩 | 陡峭(回調思維) | 平緩 | 平緩 |
| 適用文檔大小 | < 10MB | 任意大小(GB 級) | < 50MB | < 100MB |
javax.xml.parsers,無第三方依賴import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.File;
public class DomParseDemo {
public static void main(String[] args) throws Exception {
// 1. 獲取工廠與解析器
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // 支援命名空間
DocumentBuilder builder = factory.newDocumentBuilder();
// 2. 解析文檔獲取 Document 根節點
Document doc = builder.parse(new File("candidate.xml"));
// 3. 獲取所有 PERSON 元素
NodeList personList = doc.getElementsByTagName("PERSON");
// 4. 遍歷提取資料
for (int i = 0; i < personList.getLength(); i++) {
Element person = (Element) personList.item(i);
String name = getTextContent(person, "NAME");
String email = getTextContent(person, "EMAIL");
System.out.printf("姓名: %s, Email: %s%n", name, email);
}
}
private static String getTextContent(Element parent, String tagName) {
NodeList list = parent.getElementsByTagName(tagName);
if (list.getLength() > 0) {
Node child = list.item(0).getFirstChild();
return child != null ? child.getNodeValue() : "";
}
return "";
}
}
private static void parseElement(Element element) {
System.out.print("<" + element.getTagName());
// 處理屬性
NamedNodeMap attrs = element.getAttributes();
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
Attr attr = (Attr) attrs.item(i);
System.out.print(" " + attr.getName() + "=\"" + attr.getValue() + "\"");
}
}
System.out.print(">");
// 遞歸處理子節點
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
switch (node.getNodeType()) {
case Node.ELEMENT_NODE:
parseElement((Element) node);
break;
case Node.TEXT_NODE:
System.out.print(node.getNodeValue().trim());
break;
case Node.COMMENT_NODE:
System.out.print("<!--" + node.getNodeValue() + "-->");
break;
}
}
System.out.print("</" + element.getTagName() + ">");
}
DefaultHandler 回調方法處理事件:startElement、characters、endElementimport org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.util.Stack;
public class SaxParseDemo {
public static void main(String[] args) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
parser.parse(new File("student.xml"), new StudentHandler());
}
}
class StudentHandler extends DefaultHandler {
private Stack elementStack = new Stack<>();
private String name, gender, age;
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
elementStack.push(qName);
// 處理屬性
for (int i = 0; i < attrs.getLength(); i++) {
System.out.println(attrs.getQName(i) + "=" + attrs.getValue(i));
}
}
@Override
public void characters(char[] ch, int start, int length) {
String currentTag = elementStack.peek();
String value = new String(ch, start, length).trim();
if (value.isEmpty()) return;
switch (currentTag) {
case "姓名": name = value; break;
case "性別": gender = value; break;
case "年齡": age = value; break;
}
}
@Override
public void endElement(String uri, String localName, String qName) {
elementStack.pop();
if ("學生".equals(qName)) { // 一個學生元素結束
System.out.printf("姓名: %s, 性別: %s, 年齡: %s%n", name, gender, age);
name = gender = age = null;
}
}
}
Format.getPrettyFormat())、XPath(需 Jaxen)<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
import org.jdom2.*;
import org.jdom2.output.*;
public class JDomCreateDemo {
public static void main(String[] args) throws Exception {
Document doc = new Document();
Element root = new Element("root");
doc.setRootElement(root);
root.addContent(new Comment("這是註解"));
Element hello = new Element("hello");
hello.setAttribute("source", "www.sohu.com");
root.addContent(hello);
Element world = new Element("world");
world.setAttribute(new Attribute("test", "hehe"));
hello.addContent(world);
world.addContent(new Element("aaa")
.setAttribute("a", "b")
.setAttribute("x", "y")
.setText("text content"));
// 格式化輸出
Format format = Format.getPrettyFormat();
format.setIndent(" ");
new XMLOutputter(format).output(doc, new FileWriter("jdom.xml"));
}
}
public class JDomParseDemo {
public static void main(String[] args) throws Exception {
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(new File("jdom.xml"));
Element root = doc.getRootElement();
Element hello = root.getChild("hello");
// 讀取屬性
for (Attribute attr : hello.getAttributes()) {
System.out.println(attr.getName() + "=" + attr.getValue());
}
// 修改:移除子元素並寫回
hello.removeChild("world");
new XMLOutputter(Format.getPrettyFormat().setIndent(" "))
.output(doc, new FileOutputStream("jdom2.xml"));
}
}
SAXReader)<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
import org.dom4j.*;
import org.dom4j.io.*;
public class Dom4jCreateDemo {
public static void main(String[] args) throws Exception {
// 方式 1: DocumentHelper 建立
Element root = DocumentHelper.createElement("student");
Document doc = DocumentHelper.createDocument(root);
root.addAttribute("name", "zhangsan");
Element hello = root.addElement("hello").addAttribute("age", "20").setText("hello");
root.addElement("world").setText("world");
// 格式化輸出到檔案
OutputFormat format = OutputFormat.createPrettyPrint();
format.setIndent(" ");
new XMLWriter(new FileOutputStream("student2.xml"), format).write(doc);
// 輸出到控制台
new XMLWriter(OutputFormat.createPrettyPrint()).write(doc);
}
}
public class Dom4jParseDemo {
public static void main(String[] args) throws Exception {
SAXReader reader = new SAXReader();
Document doc = reader.read(new File("student2.xml"));
Element root = doc.getRootElement();
System.out.println("根元素: " + root.getName());
// 1. 獲取所有子元素
List children = root.elements();
System.out.println("子元素總數: " + children.size());
// 2. 獲取指定名稱子元素
List helloList = root.elements("hello");
System.out.println("hello 元素數: " + helloList.size());
// 3. 獲取第一個 hello 元素的屬性
Element firstHello = root.element("hello");
System.out.println("age 屬性: " + firstHello.attributeValue("age"));
// 4. 迭代器遍歷
for (Element e : root.elements()) {
System.out.println("元素: " + e.getName() + ", age=" + e.attributeValue("age"));
}
// 5. XPath 查詢(核心優勢)
// Element node = (Element) doc.selectSingleNode("//hello[@age='20']");
// List nodes = doc.selectNodes("//world");
// 6. 與標準 DOM 互轉
DOMReader domReader = new DOMReader();
org.w3c.dom.Document w3cDoc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new File("student2.xml"));
Document dom4jDoc = domReader.read(w3cDoc);
}
}
XMLStreamReader,更現代的拉式解析)JDK 6+ 內建 javax.xml.stream,提供 拉式 解析,兼具 SAX 低記憶體與 DOM 的控制流優勢:
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamReader.START_ELEMENT) {
System.out.println("元素: " + reader.getLocalName());
} else if (event == XMLStreamReader.CHARACTERS) {
System.out.println("文本: " + reader.getText());
}
}
reader.close();
新專案建議優先評估 StAX 或 Dom4j,避免直接使用較底層的 SAX。
XML 解析無絕對最優,只有最適合。理解四大方式的記憶體模型、存取模式、擴充能力三大核心差異,結合文檔大小、業務複雜度、效能指標、團隊熟悉度四大決策因子,即可做出理性技術選型。現代 Java 專案中,Dom4j 與 StAX 是最推薦的兩大主力選擇。