SAX(Simple API for XML)並非 W3C 官方標準,而是由開發社群共同推動的事實標準。儘管出身「民間」,它在 XML 解析領域的地位絲毫不遜於 DOM,幾乎所有主流 XML 解析器皆提供支援。
與 DOM 需將整份文檔載入記憶體構建樹狀結構不同,SAX 採用事件驅動、流式處理機制。解析器邊讀取 XML 邊觸發事件(如元素開始、字符內容、元素結束),透過回調函數交由開發者實現的 ContentHandler 處理。這意味著:
org.xml.sax.ContentHandler 是 SAX 處理的核心介面,主要方法包括:
| 方法 | 觸發時機 | 說明 |
|---|---|---|
startDocument() | 文檔開始 | 初始化資源、重置狀態 |
endDocument() | 文檔結束 | 釋放資源、輸出結果 |
startElement(uri, localName, qName, attrs) | 遇到開始標籤 | 取得元素名稱與屬性列表 attrs |
endElement(uri, localName, qName) | 遇到結束標籤 | 標記該元素處理完畢 |
characters(ch, start, length) | 讀到字符內容 | 需自行從字符陣列提取字串:new String(ch, start, length) |
實務上常繼承 DefaultHandler(已實作所有方法為空),僅覆寫需要的方法,簡化開發。
public class SAXCounter extends DefaultHandler {
private Map<String, Integer> tags = new HashMap<>();
@Override
public void startDocument() { tags.clear(); }
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
tags.merge(localName, 1, Integer::sum);
}
@Override
public void endDocument() {
tags.forEach((tag, count) -> System.out.println("Tag <" + tag + "> occurs " + count + " times"));
}
public static void main(String[] args) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.parse(new File("links.xml"), new SAXCounter());
}
}
關鍵點:
SAXParserFactory 解耦具體解析器實現(工廠模式)。SAXParser 封裝了 XMLReader,提供更豐富的 parse() 多載。由於 SAX 不保留父子關係,若需根據層級判斷語義(如只在 <link> 結束時輸出),需自行維護堆疊:
public class SAXReader extends DefaultHandler {
private Deque<String> tagStack = new ArrayDeque<>();
private String text, url, author, description, day, month, year;
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
tagStack.push(localName);
}
@Override
public void endElement(String uri, String localName, String qName) {
tagStack.pop();
if ("link".equals(localName)) printOut(); // 一筆資料收集完畢
}
@Override
public void characters(char[] ch, int start, int length) {
String currentTag = tagStack.peek();
String value = new String(ch, start, length);
switch (currentTag) {
case "text": text = value; break;
case "url": url = value; break;
case "author": author = value; break;
case "day": day = value; break;
case "month": month = value; break;
case "year": year = value; break;
case "description": description = value; break;
}
}
private void printOut() {
System.out.println("Content: " + text);
System.out.println("URL: " + url);
System.out.println("Author: " + author);
System.out.println("Date: " + day + "-" + month + "-" + year);
System.out.println("Description: " + description);
System.out.println();
}
// main 方法同範例一,改用 saxParser.parse(file, new SAXReader())
}
堆疊底部到頂部即為從根節點到當前節點的唯一路徑,若需精確判斷位置,可遍歷堆疊(Vector 繼承關係允許隨機存取)。
| 維度 | DOM | SAX |
|---|---|---|
| 記憶體佔用 | 高(整樹駐留) | 極低(流式) |
| 隨機存取 | 支援 | 不支援 |
| 編程複雜度 | 低 | 中高(需自管狀態) |
| 適用場景 | 小檔案、頻繁修改、XPath 查詢 | 大檔案、單向讀取、記憶體敏感環境 |
兩者皆為語言中立標準,Java 只是眾多實現語言之一。實務上常搭配使用:SAX 快速篩選感興趣片段,再以 DOM 精細操作。