背景
JAXP (Java API for XML Processing) 是 Java 處理 XML 的標準 API,支援 DOM、SAX、XSLT,通過 Pluggability Layer 解耦具體解析器 (Xerces、Crimson) 與 XSLT 處理器 (Xalan)。常見困惑:DOM 樹內存修改後,如何同步持久化到原 XML 文件?JAXP 本身無直接「保存」方法,需借助特定實現或 XSLT 引擎輸出。
四種方法對比
方法一:手動遍歷 DOM 寫入文件 (最原始)
- 遞歸遍歷 DOM 節點,逐個寫入
FileOutputStream。
- 極其繁瑣、易錯、需自行處理轉義、編碼、格式化。
- 僅適用於開發自定義 XML 解析器,業務開發**強烈不推薦**。
方法二:Apache Crimson 專有 XmlDocument.write() (已過時)
// 需依賴 crimson.jar
import org.apache.crimson.tree.XmlDocument;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
Document doc = parser.parse("user.xml");
// 修改 DOM...
Element newEl = doc.createElement("fancy");
doc.getDocumentElement().appendChild(newEl);
// 強轉調用專有方法
((XmlDocument) doc).write(new FileOutputStream("xuser1.xml")); // 默認 UTF-8
// 或指定編碼
((XmlDocument) doc).write(new OutputStreamWriter(new FileOutputStream("xuser1.xml"), "GB2312"));
- 優點:代碼極簡,直接持久化。
- 缺點:綁定 Crimson 實現 (JAXP 1.2+ 已切換 Xerces/Xalan),高版本 JAXP/Java XML Pack/JWSDP **不再內置 Crimson**,移植性極差;性能較低。
- 現狀:僅適用於老舊項目維護,新項目**禁用**。
方法三:JAXP 標準 Transformer (XSLT 引擎輸出) —— **推薦標準做法**
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
// 修改後的 DOM Document 對象
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File("output.xml"));
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
// 關鍵:設置輸出屬性 (編碼、縮進、聲明等)
Properties props = transformer.getOutputProperties();
props.setProperty(OutputKeys.ENCODING, "GB2312"); // 解決中文亂碼
props.setProperty(OutputKeys.INDENT, "yes"); // 美化縮進
props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 保留 XML 聲明
transformer.setOutputProperties(props);
// 執行轉換 (無 XSLT 樣式表時為身份轉換,即原樣輸出)
transformer.transform(source, result);
OutputKeys 常用屬性
| 屬性常量 | 典型值 | 作用 |
METHOD | xml / html / text | 輸出格式 |
VERSION | 1.0 / 4.0 | 規範版本 |
ENCODING | UTF-8 / GB2312 | 字符編碼 |
OMIT_XML_DECLARATION | yes / no | 是否省略 <?xml ...?> |
INDENT | yes / no | 是否自動縮進 |
MEDIA_TYPE | text/xml 等 | MIME 類型 |
- 優點:JAXP 標準 API,實現無關 (Xerces/Xalan/Crimson 均支持);靈活控制輸出格式;可輸出至任意
Result (文件、流、DOM、SAX 事件流)。
- 缺點:大文檔內存壓力大 (需完整 DOM 树)。
方法四:Xalan 專有 Serializer (方法三變體)
- 流程:DOM →
Transformer → DOMResult → SerializerFactory.getSerializer() → 序列化輸出流。
- 引入序列化器,適合大量文檔修改/輸出場景。
- 缺點:需重複設置 Transformer 與 Serializer 兩套屬性;依賴 Xalan/Xerces,通用性較差;代碼繁瑣。
- 適用:高性能批量序列化場景,且已鎖定 Xalan 技術棧。
總結與選型建議
| 方法 | 通用性 | 易用性 | 格式控制 | 性能 | 推薦指數 |
| 手動遍歷 | 高 | 極低 | 自行實現 | 低 | ⭐ (僅教學/造輪子) |
| Crimson XmlDocument | 極低 | 高 | 弱 | 低 | ⭐⭐ (僅舊項目維護) |
| Transformer (標準) | 高 | 高 | 強 | 中 | ⭐⭐⭐⭐⭐ 首選 |
| Xalan Serializer | 中 | 中 | 強 | 高 (批量) | ⭐⭐⭐ (特定場景) |
其他替代方案 (補充)
- JDOM / DOM4J:更友好的 Java 原生 DOM API,提供
XMLOutputter 直接輸出,代碼更簡潔。
- Castor / JAXB:數據綁定框架,對象樹
marshal() 直接生成 XML。
- StAX (JSR-173):流式寫入 (
XMLStreamWriter),超大文檔低內存首選。
最佳實踐清單
- **首選標準 Transformer**,設置
OutputKeys.ENCODING、INDENT 等屬性。
- 大文檔 (>50MB) 改用 StAX 流式寫入 或 JDOM/DOM4J。
- 頻繁修改同一文檔,考慮 數據綁定 (JAXB) 或 數據庫 XMLType 替代文件系統。
- 輸出前校驗 XML 合法性 (Schema/DTD),避免生成損壞文件。