1. 認識流式解析
在 Java 中,處理 XML 歷來有兩種主要方法。
我們已知道,DOM (Document Object Model) 會在記憶體中建立整棵文件樹。這樣瀏覽與編輯元素都很方便,但對大型檔案而言成本太高:記憶體很快就會耗盡。
相對地,SAX (Simple API for XML) 會順序處理檔案,遇到標籤時觸發事件。它非常省記憶體,能處理巨大的文件。但需要撰寫許多事件處理器,而且無法在結構中回到先前位置。
StAX (Streaming API for XML) 是一種折衷。它同樣是串流式(像 SAX),但給開發者更多掌控:需要時由我們自己「拉取」事件。這種方式稱為 pull 解析,能寫出更直觀且彈性的程式碼。
StAX 簡介
StAX (Streaming API for XML) 是 Java 的現代串流式 XML 解析器,於 JDK 6+ 引入。
核心概念:pull 解析(「拉取式」解析器)。
與 SAX 不同,在 SAX 中解析器會主動呼叫你的方法(push 模型);而在 StAX 中,你自行控制過程:
你主動向解析器請求:「給我下一個事件!」
類比:
SAX 像是電視廣播:事件不停向你湧來,你得即時回應。
StAX 像是服務中,你準備好時自己按「下一個影片」。
StAX 的關鍵類別
要使用 StAX,你需要套件 javax.xml.stream 中兩個主要類別:
- XMLInputFactory — 建立解析器的工廠。
- XMLStreamReader — 串流式解析器,以「一小段一小段」讀取 XML。
基本範例:
import javax.xml.stream.*;
import java.io.FileInputStream;
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (reader.hasNext()) {
int event = reader.next();
// 處理事件
}
reader.close();
2. StAX 的運作原理:pull 模型
在 StAX 中,你自行掌控 XML 的讀取:
- 開啟串流(例如檔案)。
- 建立 XMLStreamReader。
- 在迴圈中呼叫 reader.next() 取得下一個事件。
- 檢查事件型別(START_ELEMENT、END_ELEMENT、CHARACTERS,等等)。
- 到達需要的位置時——處理資料。
- 關閉解析器。
工作流程:
flowchart TD
A[開啟 XMLStreamReader] --> B{hasNext?}
B -- 是 --> C["next()"]
C --> D{事件型別?}
D -- START_ELEMENT --> E[處理元素開始]
D -- CHARACTERS --> F[處理文字]
D -- END_ELEMENT --> G[處理元素結束]
D -- END_DOCUMENT --> H[結束]
B -- 否 --> H
有什麼好處?
- 你可自行決定何時讀取下一個元素。
- 可以在需要的位置「停下來」,只處理檔案的一部分。
- 不必像 SAX 一樣寫一堆處理器。
3. StAX 的事件型別
當你呼叫 reader.next() 時,解析器會回傳事件型別——一個整數(介面 XMLStreamConstants 的常數)。主要事件型別如下:
- START_ELEMENT — XML 元素開始(<tag>)。
- END_ELEMENT — XML 元素結束(</tag>)。
- CHARACTERS — 標籤之間的文字內容。
- END_DOCUMENT — 文件結束。
事件處理範例:
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String name = reader.getLocalName();
System.out.println("元素開始: " + name);
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) {
System.out.println("文字: " + text);
}
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("元素結束: " + reader.getLocalName());
break;
}
}
4. 實用細節
何時使用 StAX?
StAX 是理想選擇,若符合以下情況:
- XML 檔非常大(GB 級),且不想整個載入記憶體。
- 只需處理文件的一部分(例如找到特定元素就停下)。
- 需要簡潔易懂的程式碼:StAX 比 SAX 更簡單,且不必寫一堆處理器。
範例任務:
- 匯入大型 XML 資料檔(例如 1C 的匯出、銀行對帳單、商品目錄)。
- 只搜尋並處理需要的元素(例如,從百萬筆資料中只處理 <transaction>)。
- 「即時」轉換 XML(例如過濾、彙整)。
DOM、SAX 與 StAX 的比較
| 方法 | 記憶體 | 易用度 | 彈性 | 適用情況 |
|---|---|---|---|---|
| DOM | 高(全部載入記憶體) | 非常簡單 | 可修改樹 | 小/中型檔,且需要編輯 XML 時 |
| SAX | 最低 | 較難(事件處理器) | 唯讀,無法回溯 | 超大型檔,簡單處理 |
| StAX | 最低 | 中等(pull 模型) | 可分段讀取,易於停下 | 大型檔,需要兼顧彈性與簡單 |
StAX 是折衷的中庸之道:
— 不像 DOM 那樣耗記憶體。
— 不需要像 SAX 那樣撰寫複雜處理器。
— 允許你掌控解析流程。
5. 範例:用 StAX 讀取大型 XML 檔
假設我們有檔案 "books.xml":
<library>
<book>
<title>Java 入門</title>
<author>Ivan Ivanov</author>
</book>
<book>
<title>進階 Java</title>
<author>Pyotr Petrov</author>
</book>
<!-- ... 許多書 ... -->
</library>
任務: 輸出所有書名。
StAX 程式碼:
import javax.xml.stream.*;
import java.io.*;
public class StaxDemo {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("books.xml"));
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT && "title".equals(reader.getLocalName())) {
reader.next(); // 前進到 CHARACTERS
System.out.println("書名: " + reader.getText());
}
}
reader.close();
}
}
優點:
- 不需要把整個檔案載入記憶體。
- 就算有上百萬本書,程式也不會「當機」。
6. 使用 StAX 的常見錯誤
錯誤 1:忘了關閉解析器或串流。 務必關閉 XMLStreamReader 與串流(InputStream),避免資源洩漏。
錯誤 2:沒有檢查事件型別。 並非所有事件都是元素的開始或結束。請檢查事件型別,否則可能得到空字串或錯過需要的資料。
錯誤 3:沒有考慮元素的巢狀層級。 若 XML 結構複雜(例如分章節的書籍),請追蹤目前的巢狀層級,避免混淆元素。
錯誤 4:對大型檔使用 DOM。 若檔案很大——不要用 DOM,否則可能拋出 OutOfMemoryError。處理大型檔請用 StAX 或 SAX。
錯誤 5:沒有處理例外。 檔案與 XML 操作可能拋出例外(XMLStreamException、IOException)。請妥善處理或往上拋出。
GO TO FULL VERSION