XML简介
XML 全称为可扩展标记语言(eXtensible Markup Language),是一种用于标记电子文件结构以便存储、传输和展现数据的标记语言。XML 被设计用来传输和存储数据,而不是用来显示数据的。与 HTML 类似,XML 也使用标签来描述数据的结构,但 XML 允许用户自定义标签,因此更加灵活。
XML 的基本语法规则包括:
- 标签需成对出现,有开始标签和结束标签,例如
...
。 - 标签可以嵌套,但必须严格按照层次结构书写。
- 属性值必须使用引号括起来。
- 区分大小写,标签名、属性名需严格区分大小写。
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); } }