詳細講解四種主流的XML解析方式,包括其原理、特點、適用場景和代碼示例。
四種方式,可以分為兩大類:
- 基於樹的解析:將整個XML文檔一次性加載到內存,形成一棵樹形結構。
- DOM
- 基於事件的解析:順序讀取XML文檔,遇到節點時觸發事件,邊讀邊解析。
- SAX
- StAX
- 綁定類解析:通過映射關係,將XML節點直接綁定到程序中的對象。
- JAXB
1. DOM - 文檔對象模型 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
核心思想
一次性將整個XML文檔讀入內存,構建一個樹形結構(Document對象)。程序可以通過操作這棵樹上的節點(Node、Element、Attribute等)來隨機訪問和修改XML的任何部分。
工作流程
- 創建解析器工廠。
- 由工廠創建解析器。
- 解析器解析XML源(文件、輸入流等),生成一個
Document對象。 - 程序從
Document對象開始,使用getElementsByTagName、getChildNodes等方法遍歷和操作節點。
代碼示例 (Java) bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
假設有 books.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book id="1">
<title>Java Programming</title>
<author>James Gosling</author>
<price>49.99</price>
</book>
<book id="2">
<title>XML Guide</title>
<author>John Doe</author>
<price>39.95</price>
</book>
</bookstore>
使用DOM解析的Java代碼:
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class DomParserExample {
public static void main(String[] args) {
try {
// 1. 創建DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 2. 創建DocumentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
// 3. 解析XML文件,獲取Document對象
Document document = builder.parse(new File("books.xml"));
// 可選:規範化文檔(合併相鄰文本節點等)
document.getDocumentElement().normalize();
// 4. 獲取所有book節點
NodeList nodeList = document.getElementsByTagName("book");
// 5. 遍歷節點列表
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
// 獲取屬性
String id = element.getAttribute("id");
System.out.println("Book ID: " + id);
// 獲取子元素內容
String title = element.getElementsByTagName("title").item(0).getTextContent();
String author = element.getElementsByTagName("author").item(0).getTextContent();
String price = element.getElementsByTagName("price").item(0).getTextContent();
System.out.printf("Title: %s, Author: %s, Price: %s\n", title, author, price);
System.out.println("---------------");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
優缺點 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 優點:
- 隨機訪問:可以方便地訪問和修改任意節點。
- 支持修改:可以輕鬆地對文檔結構進行增刪改查。
- 易於編程:API直觀,符合面向對象思想。
- 缺點:
- 內存消耗大:整個文檔必須加載到內存中,不適合處理大型XML文件(如幾個GB)。
- 性能相對較低:解析和構建整個樹結構需要時間。
適用場景 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 需要隨機訪問XML文檔的任意部分。
- XML文檔大小可控(通常小於幾十MB)。
- 需要對XML結構進行修改。
2. SAX - 簡單API for XML
核心思想
基於事件驅動的推模型。解析器順序讀取XML文檔,當遇到文檔開始、元素開始、元素結束、文本內容等事件時,會調用程序中預先註冊的回調方法。程序不持有整個文檔的結構,只能被動地接收事件。
工作流程 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 創建SAXParserFactory和SAXParser。
- 創建一個繼承自
DefaultHandler的處理器。 - 重寫處理器中的關鍵方法(如
startElement,endElement,characters)。 - 解析器開始解析,在解析過程中自動調用處理器的相關方法。
代碼示例 (Java) bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
public class SaxParserExample {
public static void main(String[] args) {
try {
// 1. 創建SAXParserFactory和SAXParser
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
// 2. 創建自定義的處理器
BookHandler handler = new BookHandler();
// 3. 開始解析
saxParser.parse(new File("books.xml"), handler);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 自定義事件處理器
class BookHandler extends DefaultHandler {
private String currentValue;
private String currentId;
private boolean inBook = false;
// 遇到元素開始時調用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentValue = ""; // 清空當前值
if ("book".equals(qName)) {
inBook = true;
// 獲取屬性
currentId = attributes.getValue("id");
System.out.println("Book ID: " + currentId);
}
}
// 遇到元素結束時調用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (inBook) {
switch (qName) {
case "title":
System.out.println("Title: " + currentValue);
break;
case "author":
System.out.println("Author: " + currentValue);
break;
case "price":
System.out.println("Price: " + currentValue);
break;
case "book":
System.out.println("---------------");
inBook = false;
break;
}
}
}
// 處理元素內的文本內容
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 累積文本內容(這個方法可能會被多次調用)
currentValue += new String(ch, start, length).trim();
}
}
優缺點 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 優點:
- 內存效率高:不需要將整個文檔加載到內存,適合處理大型XML文件。
- 速度快:因為是流式解析,延遲低。
- 缺點:
- 只讀:無法修改XML文檔。
- 編程複雜:需要維護解析狀態,邏輯相對複雜。
- 隨機訪問能力差:無法直接跳轉到某個特定節點。
適用場景 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 只需要讀取XML數據,不需要修改。
- 處理非常大的XML文件。
- 只需要XML中的部分數據,可以在找到所需數據後停止解析。
3. StAX - 用於XML的流API bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
核心思想
基於迭代器的拉模型。程序可以主動地從解析器中“拉取”事件(如開始元素、結束元素),控制權在程序手中。它結合了SAX的高效和DOM的易用性。
工作流程 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 創建XMLInputFactory。
- 創建XMLEventReader(或XMLStreamReader)。
- 使用
hasNext()和nextEvent()循環遍歷解析事件。 - 根據事件的類型(START_ELEMENT, CHARACTERS, END_ELEMENT)進行相應處理。
代碼示例 (Java) bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.events.*;
import java.io.FileReader;
public class StaxParserExample {
public static void main(String[] args) {
try {
// 1. 創建XMLInputFactory
XMLInputFactory factory = XMLInputFactory.newInstance();
// 2. 創建XMLEventReader
XMLEventReader eventReader = factory.createXMLEventReader(new FileReader("books.xml"));
String currentId = null;
String currentTag = null;
// 3. 迭代處理事件
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
StartElement startElement = event.asStartElement();
String qName = startElement.getName().getLocalPart();
if ("book".equals(qName)) {
// 獲取屬性
Attribute idAttr = startElement.getAttributeByName(new javax.xml.namespace.QName("id"));
if (idAttr != null) {
currentId = idAttr.getValue();
System.out.println("Book ID: " + currentId);
}
}
currentTag = qName; // 記錄當前標籤名
break;
case XMLStreamConstants.CHARACTERS:
Characters characters = event.asCharacters();
String data = characters.getData().trim();
if (data.length() > 0 && currentTag != null) {
switch (currentTag) {
case "title":
System.out.println("Title: " + data);
break;
case "author":
System.out.println("Author: " + data);
break;
case "price":
System.out.println("Price: " + data);
break;
}
}
break;
case XMLStreamConstants.END_ELEMENT:
EndElement endElement = event.asEndElement();
if ("book".equals(endElement.getName().getLocalPart())) {
System.out.println("---------------");
currentTag = null;
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
優缺點
- 優點:
- 內存效率高:與SAX一樣是流式處理。
- 控制靈活:拉模型讓程序可以控制解析流程,更容易處理複雜邏輯。
- 易於使用:相比SAX,API更直觀,不需要複雜的回調。
- 缺點:
- 只讀:標準StAX API是隻讀的(雖然有擴展可以支持寫入)。
適用場景 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 需要流式解析的高性能和高效率。
- 希望比SAX更直觀地控制解析過程。
- 需要過濾XML文檔,只處理感興趣的部分。
4. JAXB - Java架構綁定 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
核心思想
通過註解將Java類與XML模式綁定。它可以將XML數據反序列化(Unmarshalling) 為Java對象,也可以將Java對象序列化(Marshalling) 為XML數據。這是一種最高級、最便捷的解析方式。
工作流程 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 創建帶有JAXB註解的Java類(或通過XSD生成)。
- 創建JAXBContext。
- 使用
Unmarshaller將XML轉換為Java對象。 - 使用
Marshaller將Java對象轉換為XML。
代碼示例 (Java) bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
1. 創建帶註解的Java類:
import javax.xml.bind.annotation.*;
@XmlRootElement(name = "bookstore")
@XmlAccessorType(XmlAccessType.FIELD)
public class Bookstore {
@XmlElement(name = "book")
private List<Book> books = new ArrayList<>();
// Getter and Setter
public List<Book> getBooks() { return books; }
public void setBooks(List<Book> books) { this.books = books; }
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {
@XmlAttribute
private String id;
@XmlElement
private String title;
@XmlElement
private String author;
@XmlElement
private double price;
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}
2. 使用JAXB解析XML:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class JaxbParserExample {
public static void main(String[] args) {
try {
// 1. 創建JAXBContext(指定包含的類)
JAXBContext context = JAXBContext.newInstance(Bookstore.class);
// 2. 創建Unmarshaller
Unmarshaller unmarshaller = context.createUnmarshaller();
// 3. 將XML反序列化為Java對象
Bookstore bookstore = (Bookstore) unmarshaller.unmarshal(new File("books.xml"));
// 4. 像操作普通Java對象一樣操作數據
for (Book book : bookstore.getBooks()) {
System.out.println("Book ID: " + book.getId());
System.out.println("Title: " + book.getTitle());
System.out.println("Author: " + book.getAuthor());
System.out.println("Price: " + book.getPrice());
System.out.println("---------------");
}
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
優缺點
- 優點:
- 開發效率極高:無需關心XML解析細節,直接操作對象。
- 類型安全:在編譯期就能發現類型錯誤。
- 代碼簡潔:大大減少了樣板代碼。
- 缺點:
- 內存消耗:與DOM類似,需要將整個對象樹加載到內存。
- 需要預定義類:需要提前創建與XML結構對應的Java類。
- 靈活性稍差:對於不規則或動態結構的XML處理起來比較麻煩。
適用場景 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- XML文檔有固定的、已知的模式(XSD)。
- 追求開發效率和代碼可維護性。
- 需要在Java對象和XML之間頻繁轉換。
總結對比 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
|
特性
|
DOM
|
SAX
|
StAX
|
JAXB
|
|
解析模型 |
樹形模型
|
事件驅動(推)
|
事件驅動(拉)
|
對象綁定
|
|
內存佔用 |
高
|
低
|
低
|
高
|
|
速度 |
慢
|
快
|
快
|
中等
|
|
是否可寫 |
是
|
否
|
是(擴展)
|
是
|
|
訪問方式 |
隨機訪問
|
順序只讀
|
順序只讀
|
隨機訪問(對象)
|
|
編程複雜度 |
簡單
|
複雜
|
中等
|
非常簡單
|
|
適用場景 |
小文件、需修改
|
大文件、只讀
|
大文件、需控制流程
|
已知模式、追求效率
|
選擇建議: bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==
- 小配置文件,且需修改:選 DOM
- 讀取超大XML日誌文件:選 SAX 或 StAX(推薦StAX)
- 已知XML結構,追求開發速度:選 JAXB
- 需要靈活控制解析過程:選 StAX