杰瑞科技汇

Java String如何高效解析XML?

我将为你介绍几种主流的方法,从最简单到最健壮,并提供详细的代码示例。

Java String如何高效解析XML?-图1
(图片来源网络,侵删)

核心思路

无论使用哪种方法,基本流程都一样:

  1. 准备 XML 字符串: 将你的 XML 内容放在一个 String 变量中。
  2. 创建输入源: 因为解析器通常不直接从 String 读取,而是从 InputStreamInputSource 读取,所以需要将 String 转换为 InputSource
  3. 选择解析器: 根据你的需求选择解析器(DOM, SAX, StAX)。
    • DOM (Document Object Model): 将整个 XML 文档读入内存,解析成一个树形结构,优点是操作方便,可以随意遍历和修改;缺点是内存消耗大,不适合处理大型 XML 文件。
    • SAX (Simple API for XML): 事件驱动的解析器,它不会将整个文档加载到内存,而是当解析器遇到 XML 元素(如开始标签、结束标签、文本内容)时,触发相应的事件(回调方法),优点是内存占用小,速度快;缺点是只能顺序读取,不能回头,操作复杂。
    • StAX (Streaming API for XML): 提供了类似 SAX 的事件驱动机制,但它允许你“拉取”(pull)事件,而不是由解析器“推送”(push),代码更直观,性能也高,是介于 DOM 和 SAX 之间的一个很好的折中方案。
  4. 执行解析: 调用解析器开始解析过程。
  5. 处理结果: 从解析器获取结果并处理。

使用 DOM 解析器 (最常用,最简单)

DOM 适合小型 XML 文件,因为它提供了最直观的树形结构来访问数据。

添加依赖 (Maven)

虽然 Java 标准库包含 javax.xml,但为了更好的类型安全(如 DocumentBuilderFactory 的配置),推荐使用 jakarta.xml,这是 Java EE 的继任者。

<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>
<!-- 实现依赖,Glassfish -->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>4.0.3</version>
</dependency>

如果你使用的是旧版 Java (Java 8 或更早),则使用 javax.xml

Java String如何高效解析XML?-图2
(图片来源网络,侵删)

代码示例

假设我们有以下 XML 字符串:

<bookstore>
    <book category="FICTION">
        <title lang="en">The Great Gatsby</title>
        <author>F. Scott Fitzgerald</author>
        <year>1925</year>
        <price>10.99</price>
    </book>
    <book category="CHILDREN">
        <title lang="en">Harry Potter</title>
        <author>J.K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
</bookstore>

Java 代码:

import jakarta.xml.parsers.DocumentBuilder;
import jakarta.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.StringReader;
public class DomParserFromString {
    public static void main(String[] args) {
        String xmlString = """
                <bookstore>
                    <book category="FICTION">
                        <title lang="en">The Great Gatsby</title>
                        <author>F. Scott Fitzgerald</author>
                        <year>1925</year>
                        <price>10.99</price>
                    </book>
                    <book category="CHILDREN">
                        <title lang="en">Harry Potter</title>
                        <author>J.K. Rowling</author>
                        <year>2005</year>
                        <price>29.99</price>
                    </book>
                </bookstore>
                """;
        try {
            // 1. 创建 DocumentBuilderFactory 和 DocumentBuilder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 2. 将 String 转换为 InputSource
            InputSource is = new InputSource(new StringReader(xmlString));
            // 3. 解析 XML 字符串,得到 Document 对象
            Document document = builder.parse(is);
            // 4. 获取所有 book 节点
            NodeList nodeList = document.getElementsByTagName("book");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    // 获取属性
                    String category = element.getAttribute("category");
                    System.out.println("Book Category: " + category);
                    // 获取子元素文本内容
                    String title = element.getElementsByTagName("title").item(0).getTextContent();
                    String author = element.getElementsByTagName("author").item(0).getTextContent();
                    String year = element.getElementsByTagName("year").item(0).getTextContent();
                    String price = element.getElementsByTagName("price").item(0).getTextContent();
                    System.out.println("  Title: " + title);
                    System.out.println("  Author: " + author);
                    System.out.println("  Year: " + year);
                    System.out.println("  Price: " + price);
                    System.out.println("-----------------------------");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用 SAX 解析器 (内存高效)

SAX 解析器通过回调方法来处理 XML,你需要实现 org.xml.sax.helpers.DefaultHandler

代码示例

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import jakarta.xml.parsers.SAXParser;
import jakarta.xml.parsers.SAXParserFactory;
import java.io.StringReader;
public class SaxParserFromString {
    public static void main(String[] args) {
        String xmlString = """
                <bookstore>
                    <book category="FICTION">
                        <title lang="en">The Great Gatsby</title>
                        <author>F. Scott Fitzgerald</author>
                        <year>1925</year>
                        <price>10.99</price>
                    </book>
                </bookstore>
                """;
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            // 创建一个自定义的 Handler
            DefaultHandler handler = new DefaultHandler() {
                boolean inBook = false;
                boolean inTitle = false;
                boolean inAuthor = false;
                boolean inYear = false;
                boolean inPrice = false;
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) {
                    if (qName.equalsIgnoreCase("book")) {
                        inBook = true;
                        System.out.println("Found a book. Category: " + attributes.getValue("category"));
                    }
                    if (inBook && qName.equalsIgnoreCase("title")) {
                        inTitle = true;
                    }
                    if (inBook && qName.equalsIgnoreCase("author")) {
                        inAuthor = true;
                    }
                    if (inBook && qName.equalsIgnoreCase("year")) {
                        inYear = true;
                    }
                    if (inBook && qName.equalsIgnoreCase("price")) {
                        inPrice = true;
                    }
                }
                @Override
                public void characters(char[] ch, int start, int length) {
                    if (inTitle) {
                        System.out.println("  Title: " + new String(ch, start, length));
                        inTitle = false;
                    }
                    if (inAuthor) {
                        System.out.println("  Author: " + new String(ch, start, length));
                        inAuthor = false;
                    }
                    if (inYear) {
                        System.out.println("  Year: " + new String(ch, start, length));
                        inYear = false;
                    }
                    if (inPrice) {
                        System.out.println("  Price: " + new String(ch, start, length));
                        inPrice = false;
                    }
                }
                @Override
                public void endElement(String uri, String localName, String qName) {
                    if (qName.equalsIgnoreCase("book")) {
                        inBook = false;
                    }
                }
            };
            // 解析
            InputSource is = new InputSource(new StringReader(xmlString));
            saxParser.parse(is, handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用 StAX 解析器 (现代且高效)

StAX 结合了 DOM 的易用性和 SAX 的高效性,你创建一个 XMLStreamReader,然后在一个循环中逐个“拉取”事件。

Java String如何高效解析XML?-图3
(图片来源网络,侵删)

代码示例

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.StringReader;
public class StaxParserFromString {
    public static void main(String[] args) {
        String xmlString = """
                <bookstore>
                    <book category="FICTION">
                        <title lang="en">The Great Gatsby</title>
                        <author>F. Scott Fitzgerald</author>
                        <year>1925</year>
                        <price>10.99</price>
                    </book>
                </bookstore>
                """;
        try {
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xmlString));
            String currentElement = null;
            String category = null;
            while (reader.hasNext()) {
                int event = reader.next();
                switch (event) {
                    case XMLStreamConstants.START_ELEMENT:
                        currentElement = reader.getLocalName();
                        if ("book".equals(currentElement)) {
                            category = reader.getAttributeValue(null, "category");
                            System.out.println("Found a book. Category: " + category);
                        }
                        break;
                    case XMLStreamConstants.CHARACTERS:
                        if (currentElement != null) {
                            String text = reader.getText().trim();
                            if (!text.isEmpty()) {
                                switch (currentElement) {
                                    case "title":
                                        System.out.println("  Title: " + text);
                                        break;
                                    case "author":
                                        System.out.println("  Author: " + text);
                                        break;
                                    case "year":
                                        System.out.println("  Year: " + text);
                                        break;
                                    case "price":
                                        System.out.println("  Price: " + text);
                                        break;
                                }
                            }
                        }
                        break;
                    case XMLStreamConstants.END_ELEMENT:
                        if ("book".equals(reader.getLocalName())) {
                            // 一本书解析完毕
                        }
                        currentElement = null; // 重置当前元素
                        break;
                }
            }
        } catch (XMLStreamException | FactoryConfigurationError e) {
            e.printStackTrace();
        }
    }
}

使用 Jackson/Gson (推荐用于 JSON,也支持 XML)

如果你已经在项目中使用了 Jackson 或 Gson 来处理 JSON,那么它们也提供了处理 XML 的能力,这通常非常方便,因为它可以将 XML 直接映射到 Java 对象(反序列化)。

添加 Jackson XML 依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.15.2</version>
</dependency>

创建 Java 模型类

// Book.java
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "book")
public class Book {
    @JacksonXmlProperty(isAttribute = true, localName = "category")
    private String category;
    @JacksonXmlProperty(localName = "title")
    private Title title;
    @JacksonXmlProperty(localName = "author")
    private String author;
    @JacksonXmlProperty(localName = "year")
    private int year;
    @JacksonXmlProperty(localName = "price")
    private double price;
    // Getters and Setters
    // ... (省略)
}
java
public class Title {
    @JacksonXmlProperty(isAttribute = true, localName = "lang")
    private String lang;
    @JacksonXmlProperty(localName = "#text")
    private String value;
    // Getters and Setters
    // ... (省略)
}

解析代码

import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;
public class JacksonXmlParser {
    public static void main(String[] args) {
        String xmlString = """
                <bookstore>
                    <book category="FICTION">
                        <title lang="en">The Great Gatsby</title>
                        <author>F. Scott Fitzgerald</author>
                        <year>1925</year>
                        <price>10.99</price>
                    </book>
                </bookstore>
                """;
        XmlMapper xmlMapper = new XmlMapper();
        try {
            // 直接将 XML 字符串映射为 List<Book>
            // 注意:Jackson 需要一个根元素,这里我们假设根元素是 book,或者使用 @JacksonXmlRootElement 注解
            // 更简单的方式是创建一个 Bookstore 类
            Bookstore bookstore = xmlMapper.readValue(xmlString, Bookstore.class);
            for (Book book : bookstore.getBooks()) {
                System.out.println("Book Category: " + book.getCategory());
                System.out.println("  Title: " + book.getTitle().getValue());
                System.out.println("  Author: " + book.getAuthor());
                System.out.println("  Year: " + book.getYear());
                System.out.println("  Price: " + book.getPrice());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// Bookstore.java
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;
@JacksonXmlRootElement(localName = "bookstore")
public class Bookstore {
    private List<Book> book;
    public List<Book> getBooks() {
        return book;
    }
    public void setBooks(List<Book> book) {
        this.book = book;
    }
}

总结与选择建议

方法 优点 缺点 适用场景
DOM 简单直观,树形结构易于遍历和修改 内存消耗大,解析速度慢,不适合大文件 小型 XML 文件,需要频繁查询和修改数据的场景
SAX 内存占用极小,解析速度快,适合大文件 代码复杂,只能顺序读取,不能修改数据 大型 XML 文件(如日志文件),内存受限的环境
StAX 内存效率高,代码比 SAX 清晰,可读性好,性能优秀 比 DOM 稍复杂 大多数 Java 应用,是处理 XML 的现代推荐方式
Jackson/Gson 极其方便,与 JSON 处理方式统一,自动映射到 POJO 需要额外依赖,对复杂 XML 映射可能需要额外配置 已经在使用 Jackson/Gson 的项目,或者希望用声明式方式处理 XML

给初学者的建议:

  • 如果你的 XML 很小(比如几百行),直接用 DOM,最简单,不容易出错。
  • 如果你的 XML 很大(比如几百兆),或者你对性能有要求,学习并使用 StAX,它在易用性和性能之间取得了很好的平衡。
  • 如果你的项目已经在用 Jackson/Gson,并且你想保持技术栈的统一,那么使用 Jackson XML 模块会非常舒服。
分享:
扫描分享到社交APP
上一篇
下一篇