1. 引言
本文将对比Java中常用的XML库和API。
这是Java XML支持系列文章的第二篇,如果想深入了解Java中的XPath支持,可以参考上一篇文章。
2. 概述
现在我们深入探讨XML处理支持,首先简单解释相关缩写术语:
Java XML支持包含多种API,各有优缺点:
SAX:基于事件的解析API,提供底层访问,内存效率高,比DOM更快(不加载整个文档树)。缺点是不支持XPath导航,虽然高效但使用复杂。
DOM:基于模型的解析器,将树形文档加载到内存,保留元素顺序,支持双向导航、读写操作和XML修改。使用简单但内存消耗大。
StAX:兼具DOM的易用性和SAX的效率,但缺少DOM的XML修改功能,且只支持单向导航。
JAXB:支持双向导航,比DOM高效,支持XML到Java类型转换和XML修改,但只能解析有效XML文档。
注意:JAXP项目已于2013年停止维护,基本不再使用。
3. XML示例
本节将展示流行实现方案,通过实际示例对比差异。所有示例使用如下XML结构:
<tutorials>
<tutorial tutId="01" type="java">
<title>Guava</title>
<description>Introduction to Guava</description>
<date>04/04/2016</date>
<author>GuavaAuthor</author>
</tutorial>
...
</tutorials>
4. DOM4J
首先看DOM4J的使用,需要添加最新版依赖。
这是最流行的XML处理库之一,支持双向读取、创建和更新文档。兼容DOM、SAX、XPath和XLST(SAX通过JAXP支持)。
示例:按ID过滤选择元素
SAXReader reader = new SAXReader();
Document document = reader.read(file);
List<Node> elements = document.selectNodes("//*[@tutId='" + id + "']");
return elements.get(0);
SAXReader
从SAX事件创建DOM4J树,获得org.dom4j.Document
后直接传入XPath字符串即可查询。
修改文档并更新文件:
for (Node node : nodes) {
Element element = (Element)node;
Iterator<Element> iterator = element.elementIterator("title");
while (iterator.hasNext()) {
Element title =(Element)iterator.next();
title.setText(title.getText() + " updated");
}
}
XMLWriter writer = new XMLWriter(
new FileWriter(new File("src/test/resources/example_updated.xml")));
writer.write(document);
writer.close();
通过elementIterator
遍历节点非常简单,修改后用XMLWriter
将DOM4J树写入文件流。
创建新文档:
Document document = DocumentHelper.createDocument();
Element root = document.addElement("XMLTutorials");
Element tutorialElement = root.addElement("tutorial").addAttribute("tutId", "01");
tutorialElement.addAttribute("type", "xml");
tutorialElement.addElement("title").addText("XML with Dom4J");
...
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(
new FileWriter(new File("src/test/resources/example_new.xml")), format);
writer.write(document);
writer.close();
DocumentHelper
提供createDocument
创建空文档,通过链式调用添加元素和属性,最后格式化输出。
5. JDOM
使用JDOM需添加依赖,其风格与DOM4J相似。
示例:获取根元素下的所有子元素
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(this.getFile());
Element tutorials = doc.getRootElement();
List<Element> titles = tutorials.getChildren("tutorial");
通过XPath按属性查询:
SAXBuilder builder = new SAXBuilder();
Document document = (Document) builder.build(file);
String filter = "//*[@tutId='" + id + "']";
XPathFactory xFactory = XPathFactory.instance();
XPathExpression<Element> expr = xFactory.compile(filter, Filters.element());
List<Element> node = expr.evaluate(document);
SAXBuilder
创建文档实例后,用JDOM2的XPathFactory
编译XPath表达式进行查询。
6. StAX
StAX从Java 6起内置JDK,无需额外依赖。示例展示获取所有元素:
首先定义数据类:
public class Tutorial {
private String tutId;
private String type;
private String title;
private String description;
private String date;
private String author;
// 标准getter/setter
}
解析代码:
List<Tutorial> tutorials = new ArrayList<>();
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader eventReader = factory.createXMLEventReader(new FileReader(this.getFile()));
Tutorial current;
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
StartElement startElement = event.asStartElement();
String qName = startElement.getName().getLocalPart();
...
break;
case XMLStreamConstants.CHARACTERS:
Characters characters = event.asCharacters();
...
break;
case XMLStreamConstants.END_ELEMENT:
EndElement endElement = event.asEndElement();
// 检查结束元素并关闭资源
break;
}
}
需要额外定义数据类存储结果,通过事件处理器单向遍历文档。可见即使简单查询也需要大量代码,这就是SAX类API的典型特点。
7. JAXB
JAXB内置JDK,无需依赖。通过注解绑定Java对象和XML,操作极其简单。
解析XML:
JAXBContext jaxbContext = JAXBContext.newInstance(Tutorials.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Tutorials tutorials = (Tutorials) jaxbUnmarshaller.unmarshal(this.getFile());
直接映射为Java对象,后续操作如同普通Java结构。
生成XML需要添加注解:
public class Tutorial {
...
public String getTutId() {
return tutId;
}
@XmlAttribute
public void setTutId(String tutId) {
this.tutId = tutId;
}
...
@XmlElement
public void setTitle(String title) {
this.title = title;
}
...
}
@XmlRootElement
public class Tutorials {
private List<Tutorial> tutorial;
// 标准getter/setter,带@XmlElement注解
}
@XmlRootElement
定义根节点,@XmlAttribute
/@XmlElement
区分属性和元素。
生成XML:
Tutorials tutorials = new Tutorials();
tutorials.setTutorial(new ArrayList<>());
Tutorial tut = new Tutorial();
tut.setTutId("01");
...
tutorials.getTutorial().add(tut);
JAXBContext jaxbContext = JAXBContext.newInstance(Tutorials.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(tutorials, file);
对象到XML的映射是处理XML最简单的方式,没有之一。
8. XPath表达式支持
复杂XPath表达式可使用Jaxen库,它支持多种对象模型(DOM/XOM/DOM4J/JDOM)。
示例:
String expression = "/tutorials/tutorial";
XPath path = new DOMXPath(expression);
List result = path.selectNodes(xmlDocument);
需添加依赖。
9. 结论
Java XML处理方案众多,选择取决于具体需求:
- 简单场景:首选JAXB,开发效率最高
- 大文件处理:SAX/StAX内存占用低
- 需要修改:DOM4J/JDOM功能全面
- 复杂查询:结合Jaxen增强XPath
踩坑提醒:避免使用已废弃的JAXP;处理超大文件时慎用DOM;JAXB要求严格符合XML Schema规范。