XML 作為通用資料交換格式,Java 生態提供了多種解析 API。本文對比四大主流技術:標準 DOM/SAX (JDK 內建)、DOM4J、JDOM,從原理、API 設計、效能、記憶體、適用場景、程式碼範例五個維度深度剖析,助你根據專案需求精準選型。
| 維度 | DOM (W3C 標準) | SAX (事件驅動) | DOM4J (開源) | JDOM (開源) |
|---|---|---|---|---|
| 解析模型 | 樹狀結構,整份文檔載入記憶體 | 事件流 (回呼),單向掃描 | 樹狀結構,支援 XPath、XSLT | 樹狀結構,Java 原生集合 API |
| 記憶體佔用 | 高 (正比文檔大小) | 極低 (不建樹) | 中等 (優化節點物件) | 中等 (輕量封裝) |
| 隨機存取 | 支援 (任意節點導航) | 不支援 (順序唯讀) | 支援 (XPath 強大查詢) | 支援 (List/Iterator) |
| 修改/寫入 | 支援 (完整 API) | 不支援 (唯讀) | 支援 (流暢 API) | 支援 (簡潔 API) |
| 效能 (大檔) | 慢 (建樹開銷大) | 最快 (串流處理) | 快 (延遲建樹、索引優化) | 快 (輕量節點) |
| 學習曲線 | 中 (標準 API 複雜) | 中 (事件思維) | 低 (直觀、文檔豐富) | 最低 (Java 風格、20/80 原則) |
| 依賴 | 無 (JDK 內建) | 無 (JDK 內建) | 需引入 dom4j.jar | 需引入 jdom2.jar |
| 命名空間支援 | 完整但繁瑣 | 完整但繁瑣 | 優雅 (XPath 原生支援) | 良好 |
| 典型適用場景 | 小檔案、需多次隨機存取/修改、配置檔 | 大檔案、單次順序讀取、記憶體受限、Android | 企業級應用、複雜 XPath 查詢、XSLT 轉換、Hibernate 內建 | 中小專案、快速開發、配置檔讀寫、REST 回應處理 |
public interface XMLDocument {
void createXML(String fileName);
void parserXML(String fileName);
}
public class DomDemo implements XMLDocument {
private Document document;
public void init() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // 重要:支援命名空間
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
} catch (ParserConfigurationException e) { throw new RuntimeException(e); }
}
public void createXML(String fileName) {
Element root = document.createElement("employees");
document.appendChild(root);
Element emp = document.createElement("employee");
root.appendChild(emp);
appendTextNode(emp, "name", "丁宏亮");
appendTextNode(emp, "sex", "m");
appendTextNode(emp, "age", "30");
writeToFile(fileName);
}
private void appendTextNode(Element parent, String tag, String text) {
Element e = document.createElement(tag);
e.appendChild(document.createTextNode(text));
parent.appendChild(e);
}
private void writeToFile(String fileName) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new DOMSource(document), new StreamResult(new File(fileName)));
} catch (TransformerException e) { throw new RuntimeException(e); }
}
public void parserXML(String fileName) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new File(fileName));
NodeList employees = doc.getDocumentElement().getChildNodes();
for (int i = 0; i < employees.getLength(); i++) {
Node emp = employees.item(i);
if (emp.getNodeType() == Node.ELEMENT_NODE) {
NodeList fields = emp.getChildNodes();
for (int j = 0; j < fields.getLength(); j++) {
Node field = fields.item(j);
if (field.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(field.getNodeName() + ": " + field.getTextContent());
}
}
}
}
} catch (Exception e) { throw new RuntimeException(e); }
}
}
public class SaxDemo implements XMLDocument {
public void createXML(String fileName) { /* SAX 唯讀,不支援建立 */ }
public void parserXML(String fileName) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
parser.parse(new File(fileName), new DefaultHandler() {
private StringBuilder textBuffer = new StringBuilder();
private String currentElement;
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
currentElement = qName;
textBuffer.setLength(0);
if (!"employees".equals(qName)) System.out.println(qName);
}
@Override
public void characters(char[] ch, int start, int length) {
textBuffer.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
if (!"employees".equals(qName) && !"employee".equals(qName)) {
System.out.println(qName + ": " + textBuffer.toString().trim());
}
}
});
} catch (Exception e) { throw new RuntimeException(e); }
}
}
public class Dom4jDemo implements XMLDocument {
public void createXML(String fileName) {
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("employees");
Element emp = root.addElement("employee");
emp.addElement("name").setText("ddvip");
emp.addElement("sex").setText("m");
emp.addElement("age").setText("29");
try (FileWriter fw = new FileWriter(fileName)) {
XMLWriter writer = new XMLWriter(fw, OutputFormat.createPrettyPrint());
writer.write(doc);
} catch (IOException e) { throw new RuntimeException(e); }
}
public void parserXML(String fileName) {
try {
SAXReader reader = new SAXReader();
Document doc = reader.read(new File(fileName));
// XPath 查詢範例
List nodes = doc.selectNodes("//employee/name | //employee/sex | //employee/age");
for (Node node : nodes) {
System.out.println(node.getName() + ": " + node.getText());
}
// 或傳統迭代
Element root = doc.getRootElement();
for (Iterator it = root.elementIterator(); it.hasNext(); ) {
Element emp = it.next();
for (Iterator jt = emp.elementIterator(); jt.hasNext(); ) {
Element field = jt.next();
System.out.println(field.getName() + ": " + field.getText());
}
}
} catch (DocumentException e) { throw new RuntimeException(e); }
}
}
public class JDomDemo implements XMLDocument {
public void createXML(String fileName) {
Element root = new Element("employees");
Document doc = new Document(root);
Element emp = new Element("employee");
root.addContent(emp);
emp.addContent(new Element("name").setText("ddvip"));
emp.addContent(new Element("sex").setText("m"));
emp.addContent(new Element("age").setText("23"));
try {
XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
out.output(doc, new FileOutputStream(fileName));
} catch (IOException e) { throw new RuntimeException(e); }
}
public void parserXML(String fileName) {
try {
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(new File(fileName));
Element root = doc.getRootElement();
List emps = root.getChildren("employee");
for (Element emp : emps) {
List fields = emp.getChildren();
for (Element field : fields) {
System.out.println(field.getName() + ": " + field.getValue());
}
}
} catch (JDOMException | IOException e) { throw new RuntimeException(e); }
}
}
| 指標 | DOM | SAX | DOM4J | JDOM2 | StAX |
|---|---|---|---|---|---|
| 解析時間 (ms) | 1200 | 350 | 420 | 480 | 300 |
| 峰值記憶體 (MB) | 180 | 12 | 65 | 70 | 15 |
| 建立/寫入時間 (ms) | 900 | N/A | 500 | 550 | 400 |
數據僅供參考,實際受 CPU、GC、JVM 參數影響。
factory.setNamespaceAware(true),否則 XPath/元素名匹配失敗。factory.setFeature("https://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("https://xml.org/sax/features/external-general-entities", false);
factory.setFeature("https://xml.org/sax/features/external-parameter-entities", false);
factory.setSchema(schema))。四大技術各有千秋:DOM 標準重、SAX 輕快唯讀、DOM4J 功能全、JDOM 用法簡。現代專案推薦:配置/小檔 → JDOM2,企業級/複雜查詢 → DOM4J,大檔/串流 → StAX/SAX,零依賴 → 標準 DOM/SAX。掌握核心 API 差異,配合策略模式封裝,即可靈活應對各類 XML 處理需求。