Java 提供四種主流 XML 解析 API,適用場景不同:
| API | 類型 | 特點 | 適用場景 |
|---|---|---|---|
| DOM | 樹模型 | 一次性載入整個文檔到內存,支持隨機訪問、修改 | 小文件(<10MB)、需頻繁修改、XPath 查詢 |
| SAX | 事件流 | 順序讀取、不佔用文檔內存、只讀、回調式 | 大文件、單次遍歷、內存受限環境 |
| StAX | 拉取流 | 游標/迭代器模式、可控制解析進程、只讀/寫入 | 大文件、需精確控制解析邏輯、性能敏感 |
| JAXB | 綁定 | XML ↔ Java 對象自動映射、註解驅動 | 配置文件、數據交換、對象序列化 |
public void parseWithDom(String filePath) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(filePath));
doc.getDocumentElement().normalize();
// 1. 獲取根元素
Element root = doc.getDocumentElement();
System.out.println("根元素: " + root.getTagName());
// 2. 遞歸遍歷所有節點
traverseNode(root, 0);
}
private void traverseNode(Node node, int depth) {
String indent = " ".repeat(depth);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element elem = (Element) node;
System.out.println(indent + "<" + elem.getTagName() + ">");
// 屬性
NamedNodeMap attrs = elem.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
System.out.println(indent + " @" + attr.getNodeName() + "=" + attr.getNodeValue());
}
} else if (node.getNodeType() == Node.TEXT_NODE && node.getTextContent().trim().length() > 0) {
System.out.println(indent + "#text: " + node.getTextContent().trim());
}
// 遞歸子節點
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
traverseNode(children.item(i), depth + 1);
}
}
public void parseWithSax(String filePath) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new DefaultHandler() {
private int depth = 0;
private StringBuilder textBuffer = new StringBuilder();
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) {
System.out.println(" ".repeat(depth) + "<" + qName + ">");
for (int i = 0; i < attrs.getLength(); i++) {
System.out.println(" ".repeat(depth + 1) + "@" + attrs.getQName(i) + "=" + attrs.getValue(i));
}
depth++;
}
@Override
public void characters(char[] ch, int start, int length) {
textBuffer.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
depth--;
String text = textBuffer.toString().trim();
if (!text.isEmpty()) {
System.out.println(" ".repeat(depth) + "#text: " + text);
}
textBuffer.setLength(0);
System.out.println(" ".repeat(depth) + "</" + qName + ">");
}
};
parser.parse(new File(filePath), handler);
}
public void parseWithStax(String filePath) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream(filePath));
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
System.out.println("<" + reader.getLocalName() + ">");
for (int i = 0; i < reader.getAttributeCount(); i++) {
System.out.println(" @" + reader.getAttributeLocalName(i) + "=" + reader.getAttributeValue(i));
}
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) System.out.println("#text: " + text);
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("</" + reader.getLocalName() + ">");
break;
}
}
reader.close();
}
// 實體類
@XmlRootElement(name = "book")
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {
@XmlAttribute private String id;
@XmlElement private String title;
@XmlElement private String author;
@XmlElement private double price;
// getter/setter/toString
}
// 解析
public List<Book> parseWithJaxb(String filePath) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Book.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
// 假設 XML 根元素為 <books> 包含多個 <book>
// 需要包裝類或使用 JAXBElement
return ...
}
public void queryWithXPath(String filePath) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(filePath);
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// 查詢所有書名
NodeList titles = (NodeList) xpath.evaluate("//book/title", doc, XPathConstants.NODESET);
for (int i = 0; i < titles.getLength(); i++) {
System.out.println(titles.item(i).getTextContent());
}
// 查詢價格 > 50 的書籍
NodeList expensive = (NodeList) xpath.evaluate("//book[price > 50]", doc, XPathConstants.NODESET);
// ...
}
XMLStreamReader 逐元素讀取,處理完即釋放。factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)。setNamespaceAware(true)。| 異常 | 原因 | 解決 |
|---|---|---|
| SAXParseException: Premature end of file | 文件為空、截斷、或 InputStream 被重複讀取 | 檢查文件完整性、確保流只讀一次 |
| MalformedByteSequenceException | 編碼不匹配(如 UTF-8 文件用 GBK 讀) | 指定正確編碼、或檢測 BOM 自動識別 |
| OutOfMemoryError | DOM 解析超大文件 | 改用 SAX/StAX |
根據文件大小、內存限制、處理邏輯選擇解析器:小文件隨機訪問用 DOM,大文件順序遍歷用 SAX/StAX,對象映射用 JAXB,精確查詢用 XPath。生產代碼務必開啟安全特性防 XXE,妥善處理編碼與異常。