杰瑞科技汇

Java XML字符串解析有哪些高效方法?

  1. DOM (Document Object Model): 将整个 XML 文档一次性加载到内存中,形成一个树形结构,优点是易于遍历和修改,缺点是如果 XML 文件很大,会消耗大量内存。
  2. SAX (Simple API for XML): 一种事件驱动的解析模型,它不会加载整个文档,而是从上到下逐行读取,当遇到某个元素(如开始标签、结束标签、文本内容)时,会触发相应的事件,优点是内存占用小,适合处理大文件,缺点是只能读取,不能修改,且编程模型相对复杂。

下面我将详细介绍如何使用这两种主流方法,以及一个更现代、更简洁的替代方案 StAX

Java XML字符串解析有哪些高效方法?-图1
(图片来源网络,侵删)

准备工作:示例 XML 字符串

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

<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book category="FICTION">
        <title lang="en">The Great Gatsby</title>
        <author>F. Scott Fitzgerald</author>
        <year>1925</year>
        <price currency="USD">15.99</price>
    </book>
    <book category="SCIENCE">
        <title lang="en">A Brief History of Time</title>
        <author>Stephen Hawking</author>
        <year>1988</year>
        <price currency="GBP">10.50</price>
    </book>
</library>

使用 DOM 解析

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

核心类

  • DocumentBuilderFactory: 创建 DocumentBuilder 的工厂。
  • DocumentBuilder: 负责将 XML 源(如文件、输入流、字符串)解析为 Document 对象。
  • Document: 代表整个 XML 文档的内存中的树形结构。
  • Node: Document, Element, Text 等所有节点的基类。
  • Element: 代表 XML 中的元素(如 <book>, <title>)。
  • NodeList: 节点列表,通常用于获取某个元素下的所有子元素。

完整代码示例

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 DomXmlParser {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book category=\"FICTION\">\n" +
                "        <title lang=\"en\">The Great Gatsby</title>\n" +
                "        <author>F. Scott Fitzgerald</author>\n" +
                "        <year>1925</year>\n" +
                "        <price currency=\"USD\">15.99</price>\n" +
                "    </book>\n" +
                "    <book category=\"SCIENCE\">\n" +
                "        <title lang=\"en\">A Brief History of Time</title>\n" +
                "        <author>Stephen Hawking</author>\n" +
                "        <year>1988</year>\n" +
                "        <price currency=\"GBP\">10.50</price>\n" +
                "    </book>\n" +
                "</library>";
        try {
            // 1. 创建 DocumentBuilderFactory 和 DocumentBuilder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 2. 将 XML 字符串转换为输入流并解析
            ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlString.getBytes());
            Document document = builder.parse(inputStream);
            // 3. 标准化文档结构(可选,但推荐)
            document.getDocumentElement().normalize();
            // 4. 获取根元素 <library>
            Element root = document.getDocumentElement();
            System.out.println("Root element: " + root.getNodeName());
            // 5. 获取所有 <book> 节点
            NodeList bookList = root.getElementsByTagName("book");
            // 6. 遍历所有 <book> 节点
            for (int i = 0; i < bookList.getLength(); i++) {
                Node bookNode = bookList.item(i);
                if (bookNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element bookElement = (Element) bookNode;
                    // 获取元素的属性
                    String category = bookElement.getAttribute("category");
                    System.out.println("\nBook Category: " + category);
                    // 获取 <title>, <author>, <year> 等子元素
                    String title = getTagValue("title", bookElement);
                    String author = getTagValue("author", bookElement);
                    String year = getTagValue("year", bookElement);
                    String price = getTagValue("price", bookElement);
                    String currency = bookElement.getElementsByTagName("price").item(0).getAttributes().getNamedItem("currency").getNodeValue();
                    System.out.println("  Title: " + title);
                    System.out.println("  Author: " + author);
                    System.out.println("  Year: " + year);
                    System.out.println("  Price: " + price + " (" + currency + ")");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 辅助方法:获取标签的文本内容
    private static String getTagValue(String tag, Element element) {
        NodeList nodeList = element.getElementsByTagName(tag).item(0).getChildNodes();
        Node node = nodeList.item(0);
        return node.getNodeValue();
    }
}

优点与缺点

  • 优点:
    • 编程模型直观,易于理解和使用。
    • 可以方便地在内存中遍历、查询、修改和创建 XML 结构。
  • 缺点:
    • 内存消耗大:将整个 XML 文档加载到内存中,不适合处理大型 XML 文件(几百MB或GB级别)。
    • 性能较低:加载整个文档需要时间,对于只读操作来说开销较大。

使用 SAX 解析

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

核心类

  • SAXParserFactory: 创建 SAXParser 的工厂。
  • SAXParser: 执行实际的解析工作。
  • org.xml.sax.helpers.DefaultHandler: 一个方便的基类,你可以继承它并重写特定方法来处理事件。
  • startElement(): 遇到开始标签时触发。
  • endElement(): 遇到结束标签时触发。
  • characters(): 遇到标签内的文本内容时触发。

完整代码示例

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.io.IOException;
public class SaxXmlParser {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book category=\"FICTION\">\n" +
                "        <title lang=\"en\">The Great Gatsby</title>\n" +
                "        <author>F. Scott Fitzgerald</author>\n" +
                "        <year>1925</year>\n" +
                "        <price currency=\"USD\">15.99</price>\n" +
                "    </book>\n" +
                "    <book category=\"SCIENCE\">\n" +
                "        <title lang=\"en\">A Brief History of Time</title>\n" +
                "        <author>Stephen Hawking</author>\n" +
                "        <year>1988</year>\n" +
                "        <price currency=\"GBP\">10.50</price>\n" +
                "    </book>\n" +
                "</library>";
        try {
            // 1. 创建 SAXParserFactory 和 SAXParser
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            // 2. 创建自定义的 Handler
            DefaultHandler handler = new DefaultHandler() {
                boolean inBook = false;
                boolean inTitle = false;
                boolean inAuthor = false;
                boolean inYear = false;
                boolean inPrice = false;
                String currentCategory;
                String currentCurrency;
                // 遇到开始标签时触发
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                    if (qName.equalsIgnoreCase("book")) {
                        inBook = true;
                        currentCategory = attributes.getValue("category");
                        System.out.println("\nBook Category: " + currentCategory);
                    }
                    if (qName.equalsIgnoreCase("title")) {
                        inTitle = true;
                    }
                    if (qName.equalsIgnoreCase("author")) {
                        inAuthor = true;
                    }
                    if (qName.equalsIgnoreCase("year")) {
                        inYear = true;
                    }
                    if (qName.equalsIgnoreCase("price")) {
                        inPrice = true;
                        currentCurrency = attributes.getValue("currency");
                    }
                }
                // 遇到结束标签时触发
                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    if (qName.equalsIgnoreCase("book")) {
                        inBook = false;
                    }
                    if (qName.equalsIgnoreCase("title")) {
                        inTitle = false;
                    }
                    if (qName.equalsIgnoreCase("author")) {
                        inAuthor = false;
                    }
                    if (qName.equalsIgnoreCase("year")) {
                        inYear = false;
                    }
                    if (qName.equalsIgnoreCase("price")) {
                        inPrice = false;
                    }
                }
                // 遇到文本内容时触发
                @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 (inTitle) {
                        System.out.println("  Title: " + value);
                    }
                    if (inAuthor) {
                        System.out.println("  Author: " + value);
                    }
                    if (inYear) {
                        System.out.println("  Year: " + value);
                    }
                    if (inPrice) {
                        System.out.println("  Price: " + value + " (" + currentCurrency + ")");
                    }
                }
            };
            // 3. 执行解析
            saxParser.parse(new ByteArrayInputStream(xmlString.getBytes()), handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

优点与缺点

  • 优点:
    • 内存效率高:任何时候内存中只保留当前节点和路径信息,非常适合解析超大 XML 文件。
    • 速度快:因为不需要构建整个树,解析速度通常比 DOM 快。
  • 缺点:
    • 只读:SAX 解析器本身不支持修改 XML。
    • 编程复杂:事件驱动的模型不如 DOM 直观,处理复杂的嵌套逻辑和混合内容(文本和元素混合)比较麻烦。
    • 随机访问困难:不能随意跳转到文档的任意部分,只能从头开始顺序解析。

使用 StAX 解析 (推荐)

StAX (Streaming API for XML) 是一种“拉” (pull) 模型的 API,介于 DOM 和 SAX 之间,它让你主动地从解析器中“拉”出事件,而不是被动地接收 SAX 的事件,它比 SAX 更灵活,比 DOM 更节省内存。

Java XML字符串解析有哪些高效方法?-图2
(图片来源网络,侵删)

核心类

  • javax.xml.stream.XMLInputFactory: 创建 XMLEventReader 的工厂。
  • javax.xml.stream.XMLEventReader: 读取 XML 事件的游标。
  • javax.xml.stream.events.XMLEvent: 代表一个 XML 事件(如开始元素、结束元素、字符等)。
  • javax.xml.stream.util.StreamReaderDelegate: (可选) 用于包装 XMLEventReader 以增加功能。

完整代码示例

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Iterator;
public class StaxXmlParser {
    public static void main(String[] args) {
        String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<library>\n" +
                "    <book category=\"FICTION\">\n" +
                "        <title lang=\"en\">The Great Gatsby</title>\n" +
                "        <author>F. Scott Fitzgerald</author>\n" +
                "        <year>1925</year>\n" +
                "        <price currency=\"USD\">15.99</price>\n" +
                "    </book>\n" +
                "    <book category=\"SCIENCE\">\n" +
                "        <title lang=\"en\">A Brief History of Time</title>\n" +
                "        <author>Stephen Hawking</author>\n" +
                "        <year>1988</year>\n" +
                "        <price currency=\"GBP\">10.50</price>\n" +
                "    </book>\n" +
                "</library>";
        try {
            // 1. 创建 XMLInputFactory 和 XMLEventReader
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLEventReader eventReader = factory.createXMLEventReader(new ByteArrayInputStream(xmlString.getBytes()));
            String currentElement = null;
            String currentCategory = null;
            String currentCurrency = null;
            // 2. 遍历所有事件
            while (eventReader.hasNext()) {
                XMLEvent event = eventReader.nextEvent();
                // 3. 判断事件类型
                if (event.isStartElement()) {
                    StartElement startElement = event.asStartElement();
                    currentElement = startElement.getName().getLocalPart();
                    if (currentElement.equals("book")) {
                        currentCategory = startElement.getAttributeByName(new javax.xml.namespace.QName("category")).getValue();
                        System.out.println("\nBook Category: " + currentCategory);
                    }
                    if (currentElement.equals("price")) {
                        currentCurrency = startElement.getAttributeByName(new javax.xml.namespace.QName("currency")).getValue();
                    }
                }
                if (event.isCharacters()) {
                    Characters characters = event.asCharacters();
                    String value = characters.getData().trim();
                    if (!value.isEmpty()) {
                        if (currentElement.equals("title")) {
                            System.out.println("  Title: " + value);
                        } else if (currentElement.equals("author")) {
                            System.out.println("  Author: " + value);
                        } else if (currentElement.equals("year")) {
                            System.out.println("  Year: " + value);
                        } else if (currentElement.equals("price")) {
                            System.out.println("  Price: " + value + " (" + currentCurrency + ")");
                        }
                    }
                }
                if (event.isEndElement()) {
                    EndElement endElement = event.asEndElement();
                    if (endElement.getName().getLocalPart().equals("book")) {
                        // 一本书处理完毕,重置状态
                        currentCategory = null;
                    }
                }
            }
        } catch (XMLStreamException | IOException e) {
            e.printStackTrace();
        }
    }
}

优点与缺点

  • 优点:
    • 内存效率高:和 SAX 一样,是流式处理,内存占用小。
    • 编程更直观:是“拉”模型,开发者可以控制解析的流程,比 SAX 的事件驱动更易于理解和管理。
    • 性能好:通常比 SAX 更快。
    • 支持读写:除了 XMLEventReader,StAX 还提供了 XMLStreamWriter 用于生成 XML。
  • 缺点:

    不如 DOM 那样可以直接随机访问和修改任意节点。


总结与如何选择

特性 DOM SAX StAX (推荐)
模型 树状模型 事件驱动 (推) 事件迭代 (拉)
内存占用 高 (整个文档) 低 (当前节点) 低 (当前节点)
性能 较慢 很快
修改能力 可以 不可以 可以 (通过 XMLStreamWriter)
易用性 非常直观 复杂 较为直观
适用场景 小型 XML 文档,需要频繁读写和修改 大型只读 XML 文档,内存受限环境 大多数场景,特别是需要高性能和流式处理的场景

选择建议:

  • 如果你的 XML 文档很小(比如几KB到几MB),并且你需要对它进行多次查询、修改或操作,使用 DOM 是最简单直接的选择。
  • 如果你的 XML 文档非常大(几十MB以上),并且你只需要读取其中的数据,使用 SAX 或 StAX,如果处理逻辑复杂,StAX 通常是更好的选择,因为它更灵活、易用。
  • 在绝大多数现代 Java 应用中,StAX 被认为是解析 XML 的最佳实践,因为它在性能、内存占用和易用性之间取得了很好的平衡。

对于更复杂的对象映射需求,还可以考虑使用 JAXB (Java Architecture for XML Binding),它能将 XML 字符串直接映射成 Java 对象,进一步简化代码。

Java XML字符串解析有哪些高效方法?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇