詳細講解四種主流的XML解析方式,包括其原理、特點、適用場景和代碼示例。

四種方式,可以分為兩大類:

  1. 基於樹的解析:將整個XML文檔一次性加載到內存,形成一棵樹形結構。
  • DOM
  1. 基於事件的解析:順序讀取XML文檔,遇到節點時觸發事件,邊讀邊解析。
  • SAX
  • StAX
  1. 綁定類解析:通過映射關係,將XML節點直接綁定到程序中的對象。
  • JAXB

1. DOM - 文檔對象模型 bG9pajNqLmNvbQ== loij3j.combG9pajNqLmNvbQ==

核心思想

一次性將整個XML文檔讀入內存,構建一個樹形結構(Document對象)。程序可以通過操作這棵樹上的節點(Node、Element、Attribute等)來隨機訪問和修改XML的任何部分。

工作流程

  1. 創建解析器工廠。
  2. 由工廠創建解析器。
  3. 解析器解析XML源(文件、輸入流等),生成一個Document對象。
  4. 程序從Document對象開始,使用getElementsByTagNamegetChildNodes等方法遍歷和操作節點。

代碼示例 (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==

  1. 創建SAXParserFactory和SAXParser。
  2. 創建一個繼承自DefaultHandler的處理器。
  3. 重寫處理器中的關鍵方法(如startElement, endElement, characters)。
  4. 解析器開始解析,在解析過程中自動調用處理器的相關方法。

代碼示例 (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==

  1. 創建XMLInputFactory。
  2. 創建XMLEventReader(或XMLStreamReader)。
  3. 使用hasNext()nextEvent()循環遍歷解析事件。
  4. 根據事件的類型(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==

  1. 創建帶有JAXB註解的Java類(或通過XSD生成)。
  2. 創建JAXBContext。
  3. 使用Unmarshaller將XML轉換為Java對象。
  4. 使用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日誌文件:選 SAXStAX(推薦StAX)
  • 已知XML結構,追求開發速度:選 JAXB
  • 需要靈活控制解析過程:選 StAX