掌握XML解析:深入比较Java中的四种解析方式

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 掌握XML解析:深入比较Java中的四种解析方式

XML简介

XML 全称为可扩展标记语言(eXtensible Markup Language),是一种用于标记电子文件结构以便存储、传输和展现数据的标记语言。XML 被设计用来传输和存储数据,而不是用来显示数据的。与 HTML 类似,XML 也使用标签来描述数据的结构,但 XML 允许用户自定义标签,因此更加灵活。

XML 的基本语法规则包括:

  1. 标签需成对出现,有开始标签和结束标签,例如 ...
  2. 标签可以嵌套,但必须严格按照层次结构书写。
  3. 属性值必须使用引号括起来。
  4. 区分大小写,标签名、属性名需严格区分大小写。

XML 的应用领域非常广泛,常用于配置文件、数据交换、Web 服务等领域。通过定义自定义的标签和数据结构,XML 使得不同系统之间可以方便地共享和传输数据,提高了数据的可读性和可靠性。

在 Java 中,我们遇到的非常多的框架例如:Spring、MyBatis 等都使用了大量的 XML 文件作为配置文件,所以 Java 对于 XML 的解析是对于这些框架而言是非常重要的。本文就给大家介绍几种 Java 中常用的 XML 解析方式。

示例XML

通过 IDEA 创建 Maven 项目,之后可以在 resources 目录下创建 books.xml,内容如下:

<bookstore>
    <book id="1">
        <name>冰与火之歌</name>
        <price>89</price>
    </book>
    <book id="2">
        <name>安徒生童话</name>
        <price>77</price>
    </book>
</bookstore>

创建 Book 实体如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    private Integer id;
    private String name;
    private Double price;
}

创建测试类如下:

public class XMLTest {
    private InputStream inputStream;
    @Before
    public void before() throws IOException {
        // 读取 XML 输入流
        inputStream = Resources.getResourceAsStream("books.xml");
    }
    
    // 编写 @Test 测试方法
}

接下来我们开始正式介绍 Java 解析 XML 的几种方式。

DOM(Document Object Model)

描述

DOM 解析器将整个 XML 文档加载到内存中,并构建一个树形结构表示整个文档,开发者可以通过操作这棵树来访问和修改 XML 文档的内容。

优点

易于使用,支持对文档的随机访问和修改。

缺点

占用内存较大,对大型文档解析性能较差。

示例

package world.xuewei;
import org.apache.ibatis.io.Resources;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import world.xuewei.mybatis.entity.Book;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
 * @author XUEW
 */
public class XMLDomTest {
    private InputStream inputStream;
    @Before
    public void before() throws IOException {
        inputStream = Resources.getResourceAsStream("books.xml");
    }
    @Test
    public void test() {
        List<Book> books = new ArrayList<>();
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            // 通过 DocumentBuilder 对象的 parser 方法加载 books.xml 文件
            Document document = db.parse(inputStream);
            // 获取所有 book 节点的集合
            NodeList bookList = document.getElementsByTagName("book");
            // 遍历每一个 book 节点
            for (int i = 0; i < bookList.getLength(); i++) {
                Book book = new Book();
                // 通过 item(i) 方法 获取一个 book 节点,索引值从 0 开始
                Node bookNode = bookList.item(i);
                // 获取 book 节点的所有属性集合
                NamedNodeMap attrs = bookNode.getAttributes();
                // 遍历 book 的属性
                for (int j = 0; j < attrs.getLength(); j++) {
                    // 通过 item(index) 方法获取 book 节点的某一个属性
                    Node attr = attrs.item(j);
                    String attrName = attr.getNodeName();
                    if ("id".equals(attrName)) {
                        book.setId(Integer.parseInt(attr.getNodeValue()));
                    }
                }
                //解析 book 节点的子节点
                NodeList childNodes = bookNode.getChildNodes();
                //遍历 childNodes 获取每个节点的节点名和节点值
                for (int k = 0; k < childNodes.getLength(); k++) {
                    // 区分出 text 类型的 node 以及 element 类型的 node
                    if (childNodes.item(k).getNodeType() == Node.ELEMENT_NODE) {
                        //获取了 element 类型节点的节点名
                        Node item = childNodes.item(k);
                        String nodeValue = item.getFirstChild().getNodeValue();
                        if ("name".equals(item.getNodeName())) {
                            book.setName(String.valueOf(nodeValue));
                        }
                        if ("price".equals(item.getNodeName())) {
                            book.setPrice(Double.valueOf(nodeValue));
                        }
                    }
                }
                books.add(book);
            }
        } catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
        }
        books.forEach(System.out::println);
    }
}

SAX(Simple API for XML)

描述

SAX 解析器是事件驱动的,逐行读取 XML 文档并触发事件,开发者通过实现事件处理接口来处理这些事件。

优点

内存消耗小,适合处理大型 XML 文档。

缺点

无法进行随机访问,只支持读取操作,不便于对数据进行修改。

示例

package world.xuewei;
import org.apache.ibatis.io.Resources;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import world.xuewei.mybatis.entity.Book;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
 * @author XUEW
 */
public class XMLSaxTest {
    private InputStream inputStream;
    @Before
    public void before() throws IOException {
        inputStream = Resources.getResourceAsStream("books.xml");
    }
    @Test
    public void test() {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            SAXParserHandler handler = new SAXParserHandler();
            parser.parse(inputStream, handler);
            handler.getBookList().forEach(System.out::println);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 自定义 SAX 解析器
     */
    public static class SAXParserHandler extends DefaultHandler {
        String value = null;
        Book book = null;
        private final ArrayList<Book> bookList = new ArrayList<>();
        public ArrayList<Book> getBookList() {
            return bookList;
        }
        int bookIndex = 0;
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // 调用 DefaultHandler 类的 startElement 方法
            super.startElement(uri, localName, qName, attributes);
            if (qName.equals("book")) {
                bookIndex++;
                book = new Book();
                // 开始解析 book 元素的属性
                int num = attributes.getLength();
                for (int i = 0; i < num; i++) {
                    if (attributes.getQName(i).equals("id")) {
                        book.setId(Integer.parseInt(attributes.getValue(i)));
                    }
                }
            }
        }
        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException {
            //调用 DefaultHandler 类的 endElement 方法
            super.endElement(uri, localName, qName);
            // 判断是否针对一本书已经遍历结束
            switch (qName) {
                case "book":
                    bookList.add(book);
                    book = null;
                    break;
                case "name":
                    book.setName(value);
                    break;
                case "price":
                    book.setPrice(Double.valueOf(value));
                    break;
            }
        }
        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {
            super.characters(ch, start, length);
            value = new String(ch, start, length);
        }
    }
}

JDOM(Java-based Document Object Model)

描述

JDOM 的目的是成为 Java 特定文档模型,它简化与 XML 的交互并且比使用 DOM 实现更快。JDOM 与 DOM 主要有两方面不同。首先,JDOM 仅使用具体类而不使用接口。这在某些方面简化了 API,但是也限制了灵活性。第二,API 大量使用了 Collections 类,简化了那些已经熟悉这些类的 Java 开发者的使用。JDOM 对于大多数 Java/XML 应用程序来说当然是有用的,并且大多数开发者发现 API 比 DOM 容易理解的多。

引入依赖:

<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom2</artifactId>
    <version>2.0.6.1</version>
</dependency>

优点

使用具体类而不是接口,简化了 DOM 的 API,大量使用了 Java 集合(Collections)类,方便了 Java 开发人员。

缺点

没有较好的灵活性,性能较差。

示例

package world.xuewei;
import org.apache.ibatis.io.Resources;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.junit.Before;
import org.junit.Test;
import world.xuewei.mybatis.entity.Book;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
 * @author XUEW
 */
public class XMLJDomTest {
    private InputStream inputStream;
    @Before
    public void before() throws IOException {
        inputStream = Resources.getResourceAsStream("books.xml");
    }
    @Test
    public void test() {
        List<Book> books = new ArrayList<>();
        SAXBuilder saxBuilder = new SAXBuilder();
        try {
            // 创建一个输入流,将 XML 文件加载到输入流中
            InputStreamReader isr = new InputStreamReader(inputStream, "UTF-8");
            // 通过 saxBuilder 的 build 方法,将输入流加载到 saxBuilder 中
            Document document = saxBuilder.build(isr);
            Element rootElement = document.getRootElement();
            // 获取根节点下的子节点的 List 集合
            List<Element> bookList = rootElement.getChildren();
            // 继续进行解析
            for (Element book : bookList) {
                Book bookEntity = new Book();
                // 解析 book 的属性集合
                List<Attribute> attrList = book.getAttributes();
                for (Attribute attr : attrList) {
                    // 获取属性名
                    String attrName = attr.getName();
                    // 获取属性值
                    String attrValue = attr.getValue();
                    if (attrName.equals("id")) {
                        bookEntity.setId(Integer.parseInt(attrValue));
                    }
                }
                // 对 book 节点的子节点的节点名以及节点值的遍历
                List<Element> bookChildren = book.getChildren();
                for (Element child : bookChildren) {
                    String value = child.getValue();
                    if (child.getName().equals("name")) {
                        bookEntity.setName(value);
                    } else if (child.getName().equals("price")) {
                        bookEntity.setPrice(Double.parseDouble(value));
                    }
                }
                books.add(bookEntity);
            }
        } catch (JDOMException | IOException e) {
            e.printStackTrace();
        }
        books.forEach(System.out::println);
    }
}

DOM4J(Document Object Model for Java)

描述

最初,它是 JDOM 的一种智能分支。它合并了许多超出基本 XML 文档表示的功能。包括集成的 XPath 支持、XML Schema 支持以及用于大文档或流化文档的基于事件的处理。它提供了构建文档表示的选项,它通过 DOM4J API 和标准 DOM 接口具有并行访问功能 。

为支持所有这些功能,DOM4J 使用接口和抽象基本类方法。DOM4J 大量使用了 API 中的 Collections 类,但是在许多情况下,它还提供一些代替方法以允许更好的性能或更直接的编码方法。直接好处是,虽然 DOM4J 付出了更复杂的 API 的代价,但是它提供了比 JDOM 大很多的灵活性。在添加灵活性、XPath 集成和对大文档办理的目标时,DOM4J 的目标与 JDOM 是一样的:针对 Java 开发者的易用性和直观操作。DOM4J 是一个十分优秀的 Java XML API,具有性能优异、功能强大和极端易用的特点,同时也是一个开放源代码的软件。

引入依赖:

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.4</version>
</dependency>

优点

大量使用了 Java 集合类,方便 Java 开发人员,同时提供一些提供性能的替代方法。性能优异、灵活性好、功能强大和易用的特点。

缺点

大量使用了接口,API较为复杂。

示例

package world.xuewei;
import org.apache.ibatis.io.Resources;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Before;
import org.junit.Test;
import world.xuewei.mybatis.entity.Book;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 * @author XUEW
 */
public class XMLDom4JTest {
    private InputStream inputStream;
    @Before
    public void before() throws IOException {
        inputStream = Resources.getResourceAsStream("books.xml");
    }
    /**
     * 测试 Dom
     */
    @Test
    public void test() {
        List<Book> books = new ArrayList<>();
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(inputStream);
            Element bookStore = document.getRootElement();
            // 通过 element 对象的 elementIterator 方法获取迭代器
            Iterator<Element> it = bookStore.elementIterator();
            // 遍历迭代器,获取根节点中的信息(书籍)
            while (it.hasNext()) {
                Book book = new Book();
                Element bookElement = it.next();
                List<Attribute> bookAttrs = bookElement.attributes();
                for (Attribute attr : bookAttrs) {
                    String attrName = attr.getName();
                    String value = attr.getValue();
                    if ("id".equals(attrName)) {
                        book.setId(Integer.parseInt(value));
                    }
                }
                Iterator<Element> itt = bookElement.elementIterator();
                while (itt.hasNext()) {
                    Element bookChild = itt.next();
                    String childName = bookChild.getName();
                    String childValue = bookChild.getStringValue();
                    if ("name".equals(childName)) {
                        book.setName(childValue);
                    }
                    if ("price".equals(childName)) {
                        book.setPrice(Double.parseDouble(childValue));
                    }
                }
                books.add(book);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        books.forEach(System.out::println);
    }
}


相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
6天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
49 6
|
4天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
13 4
|
7天前
|
Java 编译器 数据库连接
Java中的异常处理机制深度解析####
本文深入探讨了Java编程语言中异常处理机制的核心原理、类型及其最佳实践,旨在帮助开发者更好地理解和应用这一关键特性。通过实例分析,揭示了try-catch-finally结构的重要性,以及如何利用自定义异常提升代码的健壮性和可读性。文章还讨论了异常处理在大型项目中的最佳实践,为提高软件质量提供指导。 ####
|
11天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
31 2
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
10天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
11 0
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
66 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
59 0

推荐镜像

更多