杰瑞科技汇

java string xml 解析

  1. DOM (Document Object Model):将整个 XML 文档读入内存,构建一个树形结构,优点是操作灵活,可以随意增删改查节点;缺点是当 XML 文件很大时,会占用大量内存,可能导致性能问题甚至内存溢出。
  2. SAX (Simple API for XML):一种事件驱动的解析模型,它不会将整个文档加载到内存,而是按顺序读取 XML 文档,当解析到某个元素(如开始标签、结束标签、文本内容等)时,会触发相应的事件,优点是内存占用非常小,适合解析大文件;缺点是只能顺序读取,不能随机访问,操作起来比 DOM 复杂。
  3. StAX (Streaming API for XML):介于 DOM 和 SAX 之间,也是一种流式 API,但它不像 SAX 那样被动地接收事件,而是允许应用程序主动“拉取”(pull)事件,优点是内存效率高,并且比 SAX 更易于编程控制。

下面我将分别介绍如何使用这三种方式来解析一个 XML 字符串,并提供完整的代码示例。

java string xml 解析-图1
(图片来源网络,侵删)

准备工作:示例 XML 字符串

我们将使用以下这个简单的 XML 字符串作为所有示例的输入:

<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book id="001">
        <title>Java Programming</title>
        <author>John Doe</author>
        <price currency="USD">39.99</price>
    </book>
    <book id="002">
        <title>Effective Java</title>
        <author>Joshua Bloch</author>
        <price currency="EUR">45.50</price>
    </book>
</library>

使用 DOM 解析

DOM 解析器会将整个 XML 字符串解析成一个 Document 对象,然后我们可以像操作树一样操作它。

步骤:

  1. 获取 DocumentBuilderFactory 实例。
  2. 创建 DocumentBuilder
  3. 将 XML 字符串解析为 Document 对象。
  4. 通过 Document 对象获取节点(如 Element),然后遍历和提取数据。

示例代码:

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
public class DomParserExample {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book id=\"001\">\n" +
                "        <title>Java Programming</title>\n" +
                "        <author>John Doe</author>\n" +
                "        <price currency=\"USD\">39.99</price>\n" +
                "    </book>\n" +
                "    <book id=\"002\">\n" +
                "        <title>Effective Java</title>\n" +
                "        <author>Joshua Bloch</author>\n" +
                "        <price currency=\"EUR\">45.50</price>\n" +
                "    </book>\n" +
                "</library>";
        try {
            // 1. 创建 DocumentBuilderFactory
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // 2. 创建 DocumentBuilder
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 3. 将 XML 字符串解析为 Document 对象
            // 使用 ByteArrayInputStream 将字符串转换为 InputStream
            Document document = builder.parse(new ByteArrayInputStream(xmlString.getBytes("UTF-8")));
            // 4. 获取所有 book 节点
            NodeList bookList = document.getElementsByTagName("book");
            for (int i = 0; i < bookList.getLength(); i++) {
                Node node = bookList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element bookElement = (Element) node;
                    // 获取 id 属性
                    String id = bookElement.getAttribute("id");
                    // 获取子元素的内容
                    String title = bookElement.getElementsByTagName("title").item(0).getTextContent();
                    String author = bookElement.getElementsByTagName("author").item(0).getTextContent();
                    // 获取 price 元素及其属性
                    Element priceElement = (Element) bookElement.getElementsByTagName("price").item(0);
                    String price = priceElement.getTextContent();
                    String currency = priceElement.getAttribute("currency");
                    System.out.println("Book ID: " + id);
                    System.out.println("  Title: " + title);
                    System.out.println("  Author: " + author);
                    System.out.println("  Price: " + price + " " + currency);
                    System.out.println("-----------------------------");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

依赖:DOM 解析是 Java 标准库的一部分,无需额外依赖。


使用 SAX 解析

SAX 解析器是事件驱动的,我们需要定义一个 Handler 来处理不同的事件(如开始元素、结束元素、遇到文本等)。

java string xml 解析-图2
(图片来源网络,侵删)

步骤:

  1. 创建 SAXParserFactory 实例。
  2. 创建 SAXParser
  3. 创建自定义的 DefaultHandler 子类,并重写其事件处理方法(如 startElement, endElement, characters)。
  4. Handler 和 XML 字符串的输入流传递给 SAXParser 进行解析。

示例代码:

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.ByteArrayInputStream;
import java.util.Stack;
public class SaxParserExample {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book id=\"001\">\n" +
                "        <title>Java Programming</title>\n" +
                "        <author>John Doe</author>\n" +
                "        <price currency=\"USD\">39.99</price>\n" +
                "    </book>\n" +
                "    <book id=\"002\">\n" +
                "        <title>Effective Java</title>\n" +
                "        <author>Joshua Bloch</author>\n" +
                "        <price currency=\"EUR\">45.50</price>\n" +
                "    </book>\n" +
                "</library>";
        try {
            // 1. 创建 SAXParserFactory
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // 2. 创建 SAXParser
            SAXParser saxParser = factory.newSAXParser();
            // 3. 创建自定义的 Handler
            DefaultHandler handler = new DefaultHandler() {
                // 使用一个栈来跟踪当前正在处理的元素
                private Stack<String> elementStack = new Stack<>();
                private String currentElement;
                private Book currentBook;
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                    currentElement = qName;
                    elementStack.push(qName);
                    if ("book".equals(qName)) {
                        currentBook = new Book();
                        currentBook.setId(attributes.getValue("id"));
                    }
                }
                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    elementStack.pop();
                    if ("book".equals(qName)) {
                        System.out.println(currentBook);
                        currentBook = null;
                    }
                    currentElement = null;
                }
                @Override
                public void characters(char[] ch, int start, int length) throws SAXException {
                    String value = new String(ch, start, length).trim();
                    if (value.isEmpty()) {
                        return;
                    }
                    if (currentBook != null) {
                        switch (currentElement) {
                            case "title":
                                currentBook.setTitle(value);
                                break;
                            case "author":
                                currentBook.setAuthor(value);
                                break;
                            case "price":
                                currentBook.setPrice(value);
                                currentBook.setCurrency(currentBook.getCurrency()); // 属性在 startElement 中处理
                                break;
                        }
                    }
                }
                // 重写 startElement 来处理属性
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                    super.startElement(uri, localName, qName, attributes);
                    currentElement = qName;
                    elementStack.push(qName);
                    if ("book".equals(qName)) {
                        currentBook = new Book();
                        currentBook.setId(attributes.getValue("id"));
                    } else if ("price".equals(qName) && currentBook != null) {
                        currentBook.setCurrency(attributes.getValue("currency"));
                    }
                }
            };
            // 4. 解析 XML
            saxParser.parse(new ByteArrayInputStream(xmlString.getBytes("UTF-8")), handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 一个简单的 Book 类来存储数据
    static class Book {
        private String id;
        private String title;
        private String author;
        private String price;
        private String currency;
        // Getters and Setters
        public void setId(String id) { this.id = id; }
        public void setTitle(String title) { this.title = title; }
        public void setAuthor(String author) { this.author = author; }
        public void setPrice(String price) { this.price = price; }
        public void setCurrency(String currency) { this.currency = currency; }
        @Override
        public String toString() {
            return "Book ID: " + id +
                   "\n  Title: " + title +
                   "\n  Author: " + author +
                   "\n  Price: " + price + " " + currency +
                   "\n-----------------------------";
        }
    }
}

依赖:SAX 解析也是 Java 标准库的一部分,无需额外依赖。


使用 StAX 解析

StAX 解析器允许我们“拉取”事件,我们创建一个 XMLStreamReader,然后在一个循环中不断调用 next() 方法来获取下一个事件,并根据事件类型进行处理。

步骤:

  1. 创建 XMLInputFactory 实例。
  2. 通过 factory.createXMLEventReader()factory.createXMLStreamReader() 创建读取器。
  3. 使用 while (reader.hasNext()) 循环,调用 reader.next() 获取事件。
  4. 使用 if (eventType == XMLEvent.START_ELEMENT) 等判断事件类型,并处理。

示例代码:

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
public class StaxParserExample {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book id=\"001\">\n" +
                "        <title>Java Programming</title>\n" +
                "        <author>John Doe</author>\n" +
                "        <price currency=\"USD\">39.99</price>\n" +
                "    </book>\n" +
                "    <book id=\"002\">\n" +
                "        <title>Effective Java</title>\n" +
                "        <author>Joshua Bloch</author>\n" +
                "        <price currency=\"EUR\">45.50</price>\n" +
                "    </book>\n" +
                "</library>";
        List<Book> books = new ArrayList<>();
        Book currentBook = null;
        String currentElementName = null;
        try {
            // 1. 创建 XMLInputFactory
            XMLInputFactory factory = XMLInputFactory.newInstance();
            // 2. 创建 XMLEventReader
            XMLEventReader eventReader = factory.createXMLEventReader(new ByteArrayInputStream(xmlString.getBytes("UTF-8")));
            // 3. 遍历事件
            while (eventReader.hasNext()) {
                XMLEvent event = eventReader.nextEvent();
                switch (event.getEventType()) {
                    case XMLStreamConstants.START_ELEMENT:
                        currentElementName = event.asStartElement().getName().getLocalPart();
                        if ("book".equals(currentElementName)) {
                            currentBook = new Book();
                            currentBook.setId(event.asStartElement().getAttributeByName("id").getValue());
                        }
                        break;
                    case XMLStreamConstants.CHARACTERS:
                        String characters = event.asCharacters().getData().trim();
                        if (currentBook != null && !characters.isEmpty()) {
                            switch (currentElementName) {
                                case "title":
                                    currentBook.setTitle(characters);
                                    break;
                                case "author":
                                    currentBook.setAuthor(characters);
                                    break;
                                case "price":
                                    currentBook.setPrice(characters);
                                    // 属性在 START_ELEMENT 事件中处理
                                    break;
                            }
                        }
                        break;
                    case XMLStreamConstants.END_ELEMENT:
                        if ("book".equals(event.asEndElement().getName().getLocalPart())) {
                            books.add(currentBook);
                            currentBook = null;
                        }
                        currentElementName = null; // 重置当前元素名
                        break;
                }
            }
            // 打印结果
            for (Book book : books) {
                System.out.println(book);
            }
        } catch (XMLStreamException e) {
            e.printStackTrace();
        }
    }
    // Book 类
    static class Book {
        private String id;
        private String title;
        private String author;
        private String price;
        private String currency;
        // Getters and Setters
        public void setId(String id) { this.id = id; }
        public void setTitle(String title) { this.title = title; }
        public void setAuthor(String author) { this.author = author; }
        public void setPrice(String price) { this.price = price; }
        public void setCurrency(String currency) { this.currency = currency; }
        public void setCurrency(String currency) { this.currency = currency; } // 假设在 price 的 start_element 中设置
        @Override
        public String toString() {
            return "Book ID: " + id +
                   "\n  Title: " + title +
                   "\n  Author: " + author +
                   "\n  Price: " + price + " " + currency +
                   "\n-----------------------------";
        }
    }
}

注意:上面的 StAX 示例中,currency 属性的处理需要稍作调整,应该在遇到 <price> 标签的 START_ELEMENT 事件时获取属性,这里为了简化,逻辑上可以调整一下,或者像 SAX 一样在 START_ELEMENT 中处理。


使用第三方库:Jackson / Gson (JSON 风格)

在现代 Java 开发中,更推荐使用第三方库,如 JacksonGson,它们可以将 XML 直接映射到 Java 对象(POJO),代码更简洁,可读性更高。

java string xml 解析-图3
(图片来源网络,侵删)

依赖 (Maven)

pom.xml 中添加 Jackson 的 XML 模块依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.15.2</version> <!-- 使用最新版本 -->
</dependency>

示例代码:

  1. 定义 Java 对象 (POJO)

    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
    import java.util.List;
    // 根注解,表示 <library> 标签对应这个类
    @JacksonXmlRootElement(localName = "library")
    public class Library {
        private List<Book> book;
        public List<Book> getBook() {
            return book;
        }
        public void setBook(List<Book> book) {
            this.book = book;
        }
    }
    class Book {
        // @JacksonXmlProperty(isAttribute = true) 表示这是标签的属性
        @JacksonXmlProperty(isAttribute = true)
        private String id;
        // localName 指定 XML 标签的名称
        @JacksonXmlProperty(localName = "title")
        private String title;
        @JacksonXmlProperty(localName = "author")
        private String author;
        @JacksonXmlProperty(localName = "price")
        private Price price;
        // Getters and Setters...
        // 为了简化,省略了,实际项目中需要
    }
    class Price {
        @JacksonXmlProperty(isAttribute = true)
        private String currency;
        @JacksonXmlProperty(localName = "#text") // 表示标签内的文本内容
        private String value;
        // Getters and Setters...
    }
  2. 解析代码

    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    import java.io.IOException;
    public class JacksonXmlParserExample {
        public static void main(String[] args) {
            String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                    "<library>\n" +
                    "    <book id=\"001\">\n" +
                    "        <title>Java Programming</title>\n" +
                    "        <author>John Doe</author>\n" +
                    "        <price currency=\"USD\">39.99</price>\n" +
                    "    </book>\n" +
                    "    <book id=\"002\">\n" +
                    "        <title>Effective Java</title>\n" +
                    "        <author>Joshua Bloch</author>\n" +
                    "        <price currency=\"EUR\">45.50</price>\n" +
                    "    </book>\n" +
                    "</library>";
            XmlMapper xmlMapper = new XmlMapper();
            try {
                // 一行代码将 XML 字符串转换为 Java 对象
                Library library = xmlMapper.readValue(xmlString, Library.class);
                // 现在可以像操作普通 Java 对象一样操作数据了
                for (Book book : library.getBook()) {
                    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().getValue() + " " + book.getPrice().getCurrency());
                    System.out.println("-----------------------------");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

总结与如何选择

特性 DOM SAX StAX Jackson/Gson
内存占用 高(加载整个文档) 低(事件驱动) 低(流式读取) 中(映射到对象)
性能 较慢(需要构建树) 非常快(尤其对于大文件)
易用性 非常高(树形结构) 较低(需处理事件) 中等(主动拉取) 非常高(声明式映射)
随机访问 可以(通过节点路径) 不可以(只能顺序) 不可以(只能顺序) 可以(通过对象属性)
修改/创建XML 容易 困难 较困难 容易(修改对象后重新序列化)
适用场景 XML 文档较小,需要频繁查询和修改数据。 XML 文档非常大,内存受限,只需读取一次。 需要流式处理,比 SAX 更精细的控制。 现代 Java 项目,追求代码简洁和可维护性。

选择建议

  • XML 很小,或者你需要频繁地查询和修改 XML 中的数据,使用 DOM
  • 如果你要处理非常大的 XML 文件(几百 MB 或 GB 级别),并且内存非常有限,使用 SAXStAX
  • 如果你想在流式处理和编程控制之间取得平衡,StAX 是一个很好的选择。
  • 在绝大多数现代 Java 项目中,强烈推荐使用 Jackson 或 Gson,它们将 XML 解析从繁琐的事件处理中解放出来,让你可以专注于业务逻辑,代码更清晰、更健壮。
分享:
扫描分享到社交APP
上一篇
下一篇