DOM4J 是 Java 操作 XML 的經典庫,支援 DOM 樹、XPath、SAX/StAX 事件流多種模式。本文涵蓋讀寫基礎、XPath 查詢、命名空間處理、大文件流式讀寫、HTML 解析、常見異常排查。
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
2.1.4
</dependency>
<dependency>
<groupId>jaxen</groupId>
jaxen
1.2.4
</dependency>
| 模式 | 記憶體 | 速度 | 適用場景 |
|---|---|---|---|
| DOM (SAXReader.read) | 載入整檔 | 快 | < 50MB、需隨機存取、XPath 查詢 |
| SAX 事件流 | 恆定 | 快 | 大文件、單向讀取、不需修改 |
| StAX (XMLStreamReader) | 恆定 | 最快 | 超大文件、高效能、需精細控制 |
public class Dom4jDemo {
public static void main(String[] args) throws Exception {
// 1. 讀取
SAXReader reader = new SAXReader();
// 支援命名空間
reader.setFeature("https://apache.org/xml/features/namespace-prefixes", true);
Document doc = reader.read(new File("books.xml"));
// 2. XPath 查詢 (需註冊命名空間)
Map<String, String> ns = Map.of("bk", "https://example.com/book");
XPath xpath = doc.createXPath("//bk:book[bk:price > 30]", ns);
List<Node> expensive = xpath.selectNodes(doc);
for (Node n : expensive) {
Element book = (Element) n;
System.out.println("書名: " + book.elementText("title"));
System.out.println("價格: " + book.elementText("price"));
System.out.println("ISBN 屬性: " + book.attributeValue("isbn"));
}
// 3. 修改並寫入
Element root = doc.getRootElement();
Element newBook = root.addElement("book").addAttribute("isbn", "999-999");
newBook.addElement("title").setText("DOM4J 實戰");
newBook.addElement("price").setText("59");
// 格式化輸出
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("books_out.xml"), format);
writer.write(doc);
writer.close();
}
}
public class LargeXmlHandler extends DefaultHandler {
private String currentElement;
private final List<Book> books = new ArrayList<>();
private Book currentBook;
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
if ("book".equals(qName)) {
currentBook = new Book();
currentBook.setIsbn(attrs.getValue("isbn"));
}
currentElement = qName;
}
@Override
public void characters(char[] ch, int start, int length) {
if (currentBook == null) return;
String value = new String(ch, start, length).trim();
if (value.isEmpty()) return;
switch (currentElement) {
case "title" -> currentBook.setTitle(value);
case "price" -> currentBook.setPrice(Double.parseDouble(value));
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if ("book".equals(qName) && currentBook != null) {
books.add(currentBook);
currentBook = null;
}
currentElement = null;
}
public List<Book> getBooks() { return books; }
}
// 使用
SAXReader reader = new SAXReader();
LargeXmlHandler handler = new LargeXmlHandler();
reader.setContentHandler(handler);
reader.read(new File("huge_books.xml"));
List<Book> books = handler.getBooks();
XMLOutputFactory factory = XMLOutputFactory.newFactory();
try (XMLStreamWriter writer = factory.createXMLStreamWriter(
new FileOutputStream("huge_out.xml"), "UTF-8")) {
writer.writeStartDocument("UTF-8", "1.0");
writer.writeStartElement("catalog");
for (int i = 1; i <= 1_000_000; i++) {
writer.writeStartElement("book");
writer.writeAttribute("isbn", "ISBN-" + i);
writer.writeStartElement("title"); writer.writeCharacters("Book " + i); writer.writeEndElement();
writer.writeStartElement("price"); writer.writeCharacters(String.valueOf(10 + i % 100)); writer.writeEndElement();
writer.writeEndElement(); // book
}
writer.writeEndElement(); // catalog
writer.writeEndDocument();
}
// DOM4J 內建 HTML 解析器 (基於 JTidy)
SAXReader htmlReader = new SAXReader(new org.dom4j.io.HTMLReader());
Document htmlDoc = htmlReader.read(new URL("https://example.com").openStream());
// 之後同樣用 XPath 查詢
List<Node> links = htmlDoc.selectNodes("//a/@href");
| 異常 | 原因 | 解決 |
|---|---|---|
NoClassDefFoundError: jaxen/XPath | 缺少 jaxen 依賴 | 加入 jaxen jar |
InvalidXPathException | 命名空間未註冊 | `xpath.setNamespaceURIs(Map)` |
| 記憶體溢出 (DOM 讀大檔) | 樹模型佔用過大 | 改用 SAX/StAX 流式 |
| 編碼亂碼 | XML 声明 encoding 與實際不符 | 指定 `reader.setEncoding("GBK")` 或輸出時 `format.setEncoding()` |
靈活切換 DOM/SAX/StAX 三種模式,配合 XPath 強大查詢,讓 DOM4J 成為 Java XML 處理的瑞士刀。