杰瑞科技汇

Java String如何高效解析XML?

下面我将详细介绍这几种方法,并提供完整的代码示例。

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

场景设定

假设我们有以下这个 XML 字符串,我们要解析它并提取出 <user> 的信息:

<users>
    <user id="101">
        <name>张三</name>
        <email>zhangsan@example.com</email>
        <roles>
            <role>admin</role>
            <role>editor</role>
        </roles>
    </user>
    <user id="102">
        <name>李四</name>
        <email>lisi@example.com</email>
        <roles>
            <role>user</role>
        </roles>
    </user>
</users>

DOM (Document Object Model) 解析

核心思想:将整个 XML 文档读入内存,构建一个树形结构,你可以随心所欲地遍历、查询、修改这棵树,最后还可以将修改写回文件。

优点

  • 非常直观,易于理解和使用。
  • 可以在内存中自由导航和修改 XML 数据。

缺点

Java String如何高效解析XML?-图2
(图片来源网络,侵删)
  • 内存消耗大:整个 XML 文档都会被加载到内存中,对于非常大的 XML 文件,这可能会导致 OutOfMemoryError
  • 速度相对较慢:因为需要完整解析和构建整个树。

适用场景:XML 文件较小,或者需要对 XML 进行多次读写和复杂操作的场景。

示例代码

import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class DomParserExample {
    public static void main(String[] args) {
        String xmlString = "<users>\n" +
                "    <user id=\"101\">\n" +
                "        <name>张三</name>\n" +
                "        <email>zhangsan@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>admin</role>\n" +
                "            <role>editor</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "    <user id=\"102\">\n" +
                "        <name>李四</name>\n" +
                "        <email>lisi@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>user</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "</users>";
        try {
            // 1. 创建 DocumentBuilderFactory 和 DocumentBuilder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 2. 将 XML 字符串解析为 Document 对象
            // 使用 ByteArrayInputStream 将字符串转换为 InputStream
            Document document = builder.parse(new ByteArrayInputStream(xmlString.getBytes()));
            // 3. 标准化文档,以便正确处理节点
            document.getDocumentElement().normalize();
            // 4. 获取所有 user 节点列表
            NodeList nodeList = document.getElementsByTagName("user");
            System.out.println("--- 使用 DOM 解析 XML ---");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    // 获取 id 属性
                    String id = element.getAttribute("id");
                    // 获取子元素 "name" 的文本内容
                    String name = element.getElementsByTagName("name").item(0).getTextContent();
                    // 获取子元素 "email" 的文本内容
                    String email = element.getElementsByTagName("email").item(0).getTextContent();
                    System.out.println("用户 ID: " + id);
                    System.out.println("  姓名: " + name);
                    System.out.println("  邮箱: " + email);
                    // 解析列表 <roles>
                    NodeList roleNodeList = element.getElementsByTagName("role");
                    System.out.print("  角色: [");
                    for (int j = 0; j < roleNodeList.getLength(); j++) {
                        System.out.print(roleNodeList.item(j).getTextContent());
                        if (j < roleNodeList.getLength() - 1) {
                            System.out.print(", ");
                        }
                    }
                    System.out.println("]\n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

SAX (Simple API for XML) 解析

核心思想事件驱动的解析方式,它不会将整个 XML 文档加载到内存,而是当解析器读到 XML 文档的某个部分(如开始标签、结束标签、文本内容等)时,会触发相应的事件(回调方法)。

优点

  • 内存效率极高:任何时候内存中只保留当前节点的信息,非常适合解析超大 XML 文件。
  • 速度快:解析过程是顺序的,没有复杂的树结构构建。

缺点

Java String如何高效解析XML?-图3
(图片来源网络,侵删)
  • 只能读,不能写:SAX 是只读的。
  • 编程模型复杂:你需要自己维护状态,事件是顺序触发的,如果需要“回头看”之前的数据,会非常麻烦。

适用场景:解析非常大的 XML 文件,或者只需要对 XML 进行一次性的顺序读取。

示例代码

  1. 创建一个 Handler 类,继承 DefaultHandler 并重写关键方法。
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;
// 自定义的 Handler,用于处理解析事件
class UserHandler extends DefaultHandler {
    private boolean inUser = false;
    private boolean inName = false;
    private boolean inEmail = false;
    private boolean inRole = false;
    private StringBuilder currentValue = new StringBuilder();
    private User currentUser;
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        switch (qName) {
            case "user":
                inUser = true;
                currentUser = new User();
                currentUser.setId(attributes.getValue("id"));
                break;
            case "name":
                inName = true;
                break;
            case "email":
                inEmail = true;
                break;
            case "role":
                inRole = true;
                break;
        }
    }
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        switch (qName) {
            case "user":
                inUser = false;
                System.out.println(currentUser);
                break;
            case "name":
                inName = false;
                currentUser.setName(currentValue.toString());
                break;
            case "email":
                inEmail = false;
                currentUser.setEmail(currentValue.toString());
                break;
            case "role":
                inRole = false;
                currentUser.addRole(currentValue.toString());
                break;
        }
        currentValue.setLength(0); // 清空 StringBuilder
    }
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        currentValue.append(ch, start, length);
    }
}
// 一个简单的 User 类来存储数据
class User {
    private String id;
    private String name;
    private String email;
    private java.util.List<String> roles = new java.util.ArrayList<>();
    // Getters and Setters
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public java.util.List<String> getRoles() { return roles; }
    public void addRole(String role) { this.roles.add(role); }
    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", roles=" + roles +
                '}';
    }
}
  1. 在主程序中使用 SAXParser
public class SaxParserExample {
    public static void main(String[] args) {
        String xmlString = "<users>\n" +
                "    <user id=\"101\">\n" +
                "        <name>张三</name>\n" +
                "        <email>zhangsan@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>admin</role>\n" +
                "            <role>editor</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "    <user id=\"102\">\n" +
                "        <name>李四</name>\n" +
                "        <email>lisi@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>user</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "</users>";
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            // 创建自定义的 Handler
            UserHandler handler = new UserHandler();
            // 解析 XML 字符串
            saxParser.parse(new ByteArrayInputStream(xmlString.getBytes()), handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

StAX (Streaming API for XML) 解析

核心思想:结合了 DOM 和 SAX 的优点,它也是一种流式、事件驱动的 API,但它允许你主动“拉取” (pull) 事件,而不是像 SAX 那样被动地接收。

优点

  • 内存效率高:和 SAX 一样,是流式的,适合大文件。
  • 编程模型更友好:因为是你主动拉取事件,所以更容易控制流程,代码比 SAX 更清晰。
  • 速度较快

缺点

  • 同样是只读的。

适用场景:与 SAX 类似,适合大文件解析,但当你觉得 SAX 的回调模型难以驾驭时,StAX 是一个很好的替代品。

示例代码

import javax.xml.stream.*;
import javax.xml.stream.events.*;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
public class StaxParserExample {
    public static void main(String[] args) {
        String xmlString = "<users>\n" +
                "    <user id=\"101\">\n" +
                "        <name>张三</name>\n" +
                "        <email>zhangsan@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>admin</role>\n" +
                "            <role>editor</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "    <user id=\"102\">\n" +
                "        <name>李四</name>\n" +
                "        <email>lisi@example.com</email>\n" +
                "        <roles>\n" +
                "            <role>user</role>\n" +
                "        </roles>\n" +
                "    </user>\n" +
                "</users>";
        try {
            // 1. 创建 XMLInputFactory
            XMLInputFactory factory = XMLInputFactory.newInstance();
            // 2. 创建 XMLEventReader
            XMLEventReader eventReader = factory.createXMLEventReader(new ByteArrayInputStream(xmlString.getBytes()));
            User currentUser = null;
            List<String> roles = new ArrayList<>();
            StringBuilder textContent = new StringBuilder();
            // 3. 遍历事件
            while (eventReader.hasNext()) {
                XMLEvent event = eventReader.nextEvent();
                if (event.isStartElement()) {
                    StartElement startElement = event.asStartElement();
                    String elementName = startElement.getName().getLocalPart();
                    if ("user".equals(elementName)) {
                        currentUser = new User();
                        currentUser.setId(startElement.getAttributeByName(new javax.xml.namespace.QName("id")).getValue());
                        roles.clear(); // 为新用户清空角色列表
                    } else if ("role".equals(elementName)) {
                        // 开始读取角色文本
                    }
                }
                if (event.isCharacters()) {
                    // 读取文本内容
                    textContent.append(event.asCharacters().getData());
                }
                if (event.isEndElement()) {
                    EndElement endElement = event.asEndElement();
                    String elementName = endElement.getName().getLocalPart();
                    if ("user".equals(elementName)) {
                        System.out.println(currentUser);
                    } else if ("name".equals(elementName)) {
                        currentUser.setName(textContent.toString());
                    } else if ("email".equals(elementName)) {
                        currentUser.setEmail(textContent.toString());
                    } else if ("role".equals(elementName)) {
                        roles.add(textContent.toString());
                        currentUser.setRoles(roles);
                    }
                    textContent.setLength(0); // 清空
                }
            }
            eventReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(注意:这个 StAX 示例为了清晰,使用了 XMLEventReader,还有一个更简单的 XMLStreamReader,它提供更底层的“游标”式访问,性能更高。)


数据绑定 (推荐)

这是目前最流行、最高效的方法,你预先定义好与 XML 结构对应的 Java 类(POJO),然后使用 Jackson 或 Gson 这样的库,一行代码就能完成 XML 字符串到 Java 对象列表的转换。

优点

  • 代码极其简洁:几乎不需要写解析逻辑。
  • 可读性和可维护性高:Java 对象的结构清晰,易于理解和使用。
  • 性能优秀:底层通常是高效的流式解析。

缺点

  • 需要引入第三方库
  • 对于非常动态、结构不固定的 XML,可能需要更多配置。

示例代码 (使用 Jackson)

  1. 添加 Maven 依赖

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

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;
// 根节点
@JacksonXmlRootElement(localName = "users")
public class UsersWrapper {
    @JacksonXmlElementWrapper(useWrapping = false) // 不包装 <user> 标签
    @JacksonXmlProperty(localName = "user") // 指定对应的 XML 标签名
    private List<User> users;
    // Getters and Setters
    public List<User> getUsers() { return users; }
    public void setUsers(List<User> users) { this.users = users; }
}
// User 类
public class User {
    @JacksonXmlProperty(isAttribute = true, localName = "id") // 标记为属性
    private String id;
    @JacksonXmlProperty(localName = "name")
    private String name;
    @JacksonXmlProperty(localName = "email")
    private String email;
    @JacksonXmlProperty(localName = "role") // roles/role -> user.roles
    private List<String> roles;
    // Getters and Setters
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public List<String> getRoles() { return roles; }
    public void setRoles(List<String> roles) { this.roles = roles; }
    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", roles=" + roles +
                '}';
    }
}
  1. 主程序
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
public class JacksonXmlParserExample {
    public static void main(String[] args) {
        String xmlString = "<users>\n" +
                "    <user id=\"101\">\n" +
                "        <name>张三</name>\n" +
                "        <email>zhangsan@example.com</email>\n" +
                "        <role>admin</role>\n" +
                "        <role>editor</role>\n" +
                "    </user>\n" +
                "    <user id=\"102\">\n" +
                "        <name>李四</name>\n" +
                "        <email>lisi@example.com</email>\n" +
                "        <role>user</role>\n" +
                "    </user>\n" +
                "</users>";
        try {
            XmlMapper xmlMapper = new XmlMapper();
            // 一行代码完成解析!
            UsersWrapper wrapper = xmlMapper.readValue(xmlString, UsersWrapper.class);
            System.out.println("--- 使用 Jackson 解析 XML ---");
            for (User user : wrapper.getUsers()) {
                System.out.println(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结与如何选择

特性 DOM SAX StAX 数据绑定 (Jackson/Gson)
内存占用 高 (整个树) 低 (流式) 低 (流式) 低 (流式)
速度 较慢
易用性 中等 复杂 (回调) 中等 非常简单
可修改性 可读写 只读 只读 只读 (除非重新序列化)
适用场景 小文件,复杂操作 超大文件,顺序读取 超大文件,需要更多控制 绝大多数现代应用,追求开发效率

选择建议

  • 新手或追求开发效率:直接使用 数据绑定 (Jackson/Gson),这是目前 Java 生态中最主流、最推荐的方式。
  • 处理超大 XML 文件:如果内存是首要考虑因素,且 XML 结构非常固定,SAX 是经典选择,如果你觉得 SAX 编程模型难以忍受,StAX 是更好的现代替代品。
  • 需要频繁修改 XML:如果解析后需要对 XML 结构进行增删改查,并且文件不大,DOM 是最直观的选择。
分享:
扫描分享到社交APP
上一篇
下一篇