雖然 JSON 已成為 API 交換主流,但 XML 在企業級配置、SOAP 服務、Office 文檔、Android 佈局、Maven POM、SVG 圖形等領域仍佔統治地位。Java 提供了豐富的 XML 處理生態,本文將系統梳理五大主流解析技術的原理、效能、適用場景,並提供生產級代碼範本。
| 維度 | DOM (W3C) | SAX | JDOM | DOM4J | XPath (JAXP) |
|---|---|---|---|---|---|
| 解析模式 | 樹模型(全量載入記憶體) | 事件流模型(流式讀取) | 樹模型(Java 原生 API) | 樹模型(高效能實現) | 查詢語言(需配合 DOM/DOM4J) |
| 記憶體佔用 | 高(文檔大小 5-10 倍) | 極低(常數級) | 中高 | 中(優化過的樹結構) | 取決於底層模型 |
| 查詢靈活性 | 完整樹遍歷 | 單向流式,無法回溯 | 友好 API | 強大(XPath、XSLT、索引) | 聲明式路徑查詢 |
| 修改寫入 | 原生支援 | 不支援(需自行構建) | 支援 | 支援(OutputFormat 格式化) | 僅查詢 |
| 學習曲線 | 標準但冗長 | 需理解事件回調 | 低(Java 風格) | 低(流式 API) | 需學習 XPath 語法 |
| 典型適用場景 | 小型配置、需隨機訪問修改 | 大文件、單次順序讀取、內存受限 | 中小項目、追求代碼簡潔 | 企業級應用、複雜 XML 處理 | 複雜條件查詢、數據提取 |
所有示例針對同一 XML 結構:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<TelePhone>
<type name="nokia">
<price>599</price>
<operator>CMCC</operator>
</type>
<type name="xiaomi">
<price>699</price>
<operator>ChinaNet</operator>
</type>
</TelePhone>
</root>
org.w3c.dom 套件,JDK 內建無需額外依賴Node 為基礎類型,Element 為最常用子類Element 代表完整元素(含屬性、子節點),Node 為抽象基類(含 Text、Attribute、Comment 等)
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
public class DomXmlHandler {
/** 創建 XML 並輸出字串與文件 */
public String createXml() throws ParserConfigurationException, TransformerException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
doc.setXmlVersion("1.0");
doc.setXmlStandalone(true);
Element root = doc.createElement("root");
doc.appendChild(root);
Element telephone = doc.createElement("TelePhone");
root.appendChild(telephone);
addPhoneType(doc, telephone, "nokia", "599", "CMCC");
addPhoneType(doc, telephone, "xiaomi", "699", "ChinaNet");
// 輸出字串
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
// 寫入文件
try (FileOutputStream fos = new FileOutputStream("TelePhone_DOM.xml")) {
transformer.transform(new DOMSource(doc), new StreamResult(fos));
}
return writer.toString();
}
private void addPhoneType(Document doc, Element parent, String name, String price, String operator) {
Element type = doc.createElement("type");
type.setAttribute("name", name);
Element priceEl = doc.createElement("price");
priceEl.setTextContent(price);
type.appendChild(priceEl);
Element opEl = doc.createElement("operator");
opEl.setTextContent(operator);
type.appendChild(opEl);
parent.appendChild(type);
}
/** 解析 XML 字串 */
public void parseXml(String xml) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
Element root = doc.getDocumentElement();
NodeList types = root.getElementsByTagName("type");
for (int i = 0; i < types.getLength(); i++) {
Element type = (Element) types.item(i);
String name = type.getAttribute("name");
String price = getChildText(type, "price");
String operator = getChildText(type, "operator");
System.out.printf("Phone: %s, Price: %s, Operator: %s%n", name, price, operator);
}
}
private String getChildText(Element parent, String tagName) {
NodeList list = parent.getElementsByTagName(tagName);
return list.getLength() > 0 ? list.item(0).getTextContent() : "";
}
}
XMLWriter + OutputFormat)依賴:dom4j + jaxen(XPath 引擎,缺失會報 NoClassDefFoundError: org/jaxen/JaxenException)
import org.dom4j.*;
import org.dom4j.io.*;
import java.io.*;
import java.util.List;
public class Dom4jXmlHandler {
public String createXml() throws IOException {
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("root");
Element telephone = root.addElement("TelePhone");
addPhone(telephone, "nokia", "599", "CMCC");
addPhone(telephone, "xiaomi", "699", "ChinaNet");
// 格式化輸出字串
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");
StringWriter writer = new StringWriter();
XMLWriter xmlWriter = new XMLWriter(writer, format);
xmlWriter.write(doc);
xmlWriter.close();
// 寫入文件
try (FileWriter fw = new FileWriter("TelePhone_DOM4J.xml")) {
XMLWriter fileWriter = new XMLWriter(fw, format);
fileWriter.write(doc);
}
return writer.toString();
}
private void addPhone(Element parent, String name, String price, String operator) {
Element type = parent.addElement("type").addAttribute("name", name);
type.addElement("price").addText(price);
type.addElement("operator").addText(operator);
}
/** 標準遍歷解析 */
public void parseXml(String xml) throws DocumentException {
SAXReader reader = new SAXReader();
Document doc = reader.read(new InputSource(new StringReader(xml)));
Element root = doc.getRootElement();
List types = root.element("TelePhone").elements("type");
for (Element type : types) {
String name = type.attributeValue("name");
List children = type.elements();
for (Element child : children) {
System.out.printf("%s: %s=%s%n", name, child.getName(), child.getText());
}
}
}
/** XPath 查詢(推薦複雜條件) */
public void parseByXPath(String xml) throws DocumentException {
SAXReader reader = new SAXReader();
Document doc = reader.read(new InputSource(new StringReader(xml)));
List types = doc.selectNodes("/root/TelePhone/type");
for (Element type : types) {
String name = type.attributeValue("name");
List props = type.selectNodes("./*");
for (Element prop : props) {
System.out.printf("%s: %s=%s%n", name, prop.getName(), prop.getText());
}
}
}
}
SAX 不建樹,通過 ContentHandler 回調事件(startElement、characters、endElement)。適合:
代碼較冗長,此處略過,核心在於維護狀態機(當前路徑、臨時數據)。
JDK 1.5+ 內建 javax.xml.xpath,可作用於任何 Document(DOM、DOM4J 轉換後)。
public class JaxpXPathHandler {
public void query(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false); // 無命名空間
Document doc = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// 編譯表達式(可重用)
XPathExpression expr = xpath.compile("/root/TelePhone/type");
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
Element type = (Element) nodes.item(i);
String name = type.getAttribute("name");
System.out.println("Phone: " + name);
XPathExpression childExpr = xpath.compile("./*");
NodeList children = (NodeList) childExpr.evaluate(type, XPathConstants.NODESET);
for (int j = 0; j < children.getLength(); j++) {
Element child = (Element) children.item(j);
System.out.printf(" %s = %s%n", child.getTagName(), child.getTextContent());
}
}
}
}
真實項目中 XML 常含命名空間:
<tg:bookstore xmlns:tg="https://example.com/tg" xmlns:ns="https://example.com/ns">
<ns:book><tg:title>Hello</tg:title></ns:book>
</tg:bookstore>
XPath 查詢必須註冊前綴映射,否則 //book 匹配不到 ns:book。
public class NamespaceContextImpl implements NamespaceContext {
private final Map map = Map.of(
"df", "https://example.com/tg", // default namespace 映射為 df
"ns", "https://example.com/ns",
"tg", "https://example.com/tg"
);
@Override public String getNamespaceURI(String prefix) { return map.getOrDefault(prefix, XMLConstants.NULL_NS_URI); }
@Override public String getPrefix(String uri) { return null; }
@Override public Iterator getPrefixes(String uri) { return null; }
}
// 使用
xpath.setNamespaceContext(new NamespaceContextImpl());
XPathExpression expr = xpath.compile("//df:book/ns:title"); // 必須帶前綴
| 操作 | DOM | SAX | DOM4J | JAXP XPath (DOM) |
|---|---|---|---|---|
| 解析時間 | ~450ms | ~180ms | ~220ms | ~480ms |
| 堆內存峰值 | ~180MB | ~5MB | ~90MB | ~180MB |
| XPath 查詢 1000 次 | N/A | N/A | ~15ms | ~25ms |
需修改寫入?
├─ 是 → 文件 < 50MB?→ DOM / DOM4J
│ └─ 文件 > 50MB?→ StAX (JDK 內建流式寫入) 或分片處理
└─ 否 → 僅查詢/讀取?
├─ 複雜條件、多次查詢 → DOM4J + XPath
├─ 單次順序讀取、大文件 → SAX / StAX
└─ 簡單提取、標準無依賴 → JAXP XPath (DOM)
com.fasterxml.jackson.dataformat:jackson-dataformat-xml,複用 JSON 註解,效能優於 JAXB,Spring Boot 2.x+ 默認整合。新項目建議優先考慮 JAXB/Jackson,僅在需動態結構、非強類型場景下使用 DOM4J/XPath。
Java XML 處理已形成成熟體系:
掌握各技術記憶體模型、線程安全性、命名空間處理、異常捕獲細節,才能在生產環境游刃有餘。