Java基础系列17:使用DOM、SAX、JDOM、DOM4J解析XML文件详解
一 简介
在Java中,可以使用多种方式来解析XML文件,其中最常见的可能就是DOM、SAX、JDOM、DOM4J这四种方式了。其中,DOM和SAX这两种解析XML文件的方式有jdk自带的API,因此不需要额外引入第三方的jar包。与之相反的是,JDOM和DOM4J这两种解析方式都是第三方开源项目,因此在使用这两种方式解析XML文件时需要额外引入相关jar包
(1)DOM
DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合,这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作
因此在使用DOM这种方式来解析XML文件时,解析器需要将整个XML文件读到内存中,形成一个树形结构方便后面的操作
优点: 整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种操作
缺点: 将整个文档调入内存(包括无用的节点),浪费时间和内存,如果XML过大容易出现内存溢出问题
(2)SAX
由于DOM解析XML文件时需要一次性读入整个文件,当文件过大时有诸多不足之处,因此为了解决这个问题,出现了SAX这种基于事件驱动的解析方式
SAX解析XML文件通过从上往下依次不断载入内容到内存中,当解析器发现元素的开始标志、结束标志、文本、文档的开始标志、文档的结束标志等相关标志时,将会触发一些对应的事件,我们需要做的就是在这些事件的方法中编写自定义代码,用于保存获取到的数据
优点:不用事先载入整个文档,占用资源(内存)少;使用SAX方式解析编写的代码要比使用DOM解析编写的代码少
缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素
(3)JDOM
使用JDOM来解析XML文件跟使用DOM来解析从代码上来说解析思路是差不多的。JDOM与DOM主要有两方面不同:首先,JDOM仅使用具体类而不使用接口,这在某些方面简化了API,但是也限制了灵活性。其次是JDOM的API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用
优点:开源项目;比DOM容易理解
缺点:JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档
(4)DOM4J
DOM4J 是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的 Java 软件都在使用 DOM4J 来读写 XML
由于DOM4J无论在性能方面还是代码编写方面都是很强大的,特别是当XML文件很大时使用DOM4J来解析也会有较高的效率。因此,建议平时需要解析XML文件可以考虑尽可能使用DOM4J来解析。当然如果文件非常小的话使用DOM来解析也是可以的
优点:
开源项目
DOM4J是JDOM的一种智能分支,它合并了需要超出基本XML文档的功能
具有性能优异、灵活性好、简单易用等特点
二 DOM解析XML文件
(1)在进行代码编写测试之前,需要准备一个XML文件,我这里准备的文件是:demo1.xml
demo1.xml:
zifangsky 10 male https://www.zifangsky.cn admin 20 male https://www.tar.pub
(2)代码实例:
package cn.zifangsky.xml; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class DomParseTest { public static void main(String[] args) { DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder dBuilder = dFactory.newDocumentBuilder(); // 加载一个xml文件 Document document = dBuilder .parse("src/cn/zifangsky/xml/demo1.xml"); // 获取user节点集合 NodeList userList = document.getElementsByTagName("user"); int userListLength = userList.getLength(); System.out.println("此xml文件一共有" + userListLength + "个'user'节点\n"); // 遍历 for (int i = 0; i从上面的代码可以看出,在使用DOM来解析XML文件时一般需要做以下几步操作:
创建一个文档构建器工厂(DocumentBuilderFactory)实例
通过上面的DocumentBuilderFactory生成一个新的文档构建器(DocumentBuilder)
使用上面的DocumentBuilder解析(parse)一个XML文件,生成文档树(Document)
通过Document获取指定id的节点或根据节点名获取所有符合条件的节点集合
遍历每个节点,可以获取该节点的属性、属性值等相关参数
如果该节点还存在子节点,可以根据上面的方式继续遍历它的所有子节点
(3)上面的代码输出如下:
此xml文件一共有2个'user'节点 'user'节点0有1个属性: 属性0: 属性名: id ,属性值: 1 属性为'id'的对应值是: 1 该节点一共有9个子节点,分别是: 节点名: name,节点值: zifangsky 节点名: age,节点值: 10 节点名: sex,节点值: male 节点名: contact,节点值: https://www.zifangsky.cn *************************** 'user'节点1有1个属性: 属性0: 属性名: id ,属性值: 2 属性为'id'的对应值是: 2 该节点一共有9个子节点,分别是: 节点名: name,节点值: admin 节点名: age,节点值: 20 节点名: sex,节点值: male 节点名: contact,节点值: https://www.tar.pub ***************************三 SAX解析XML文件
在进行本次测试时,并不引入其他XML文件,仍然使用上面的demo1.xml文件
由于SAX解析XML文件跟DOM不同,它并不是将整个文档都载入到内存中。解析器在解析XML文件时,通过逐步载入文档,从上往下一行行的解析XML文件,在碰到文档开始标志、节点开始标志、文本文档、节点结束标志、文档结束标志时进行对应的事件处理。因此,我们首先需要构造一个这样的解析处理器来申明:当解析到这些标志时,我们需要进行怎样的自定义处理
(1)解析处理器SAXParseHandler.java:
package cn.zifangsky.xml; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SAXParseHandler extends DefaultHandler { /** * 用来遍历XML文件的开始标签 * */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); //解析'user'元素的属性值 // if(qName.equals("user")) // System.out.println("'user'元素的id属性值是:" + attributes.getValue("id")); //遍历并打印元素的属性 int length = attributes.getLength(); if(length > 0){ System.out.println("元素'" + qName + "'的属性是:"); for(int i=0;i"); } /** * 用来遍历XML文件的结束标签 * */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); System.out.println(""); } /** * 文本内容 * */ public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); String value = new String(ch, start, length).trim(); if(!value.equals("")) System.out.print(value); } /** * 用来标识解析开始 * */ @Override public void startDocument() throws SAXException { System.out.println("SAX解析开始"); super.startDocument(); } /** * 用来标识解析结束 * */ @Override public void endDocument() throws SAXException { System.out.println("SAX解析结束"); super.endDocument(); } } 关于上面代码的一些含义我这里就不再做解释了,可以自行参考注释内容
(2)测试:
SAXParseTest.java文件:
package cn.zifangsky.xml; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; public class SAXParseTest { public static void main(String[] args) { SAXParserFactory sFactory = SAXParserFactory.newInstance(); try { SAXParser saxParser = sFactory.newSAXParser(); //创建自定义的SAXParseHandler解析类 SAXParseHandler saxParseHandler = new SAXParseHandler(); saxParser.parse("src/cn/zifangsky/xml/demo1.xml", saxParseHandler); } catch (Exception e) { e.printStackTrace(); } } }从上面的代码可以看出,使用SAX解析XML文件时,一共传递进去了两个参数,分别是:XML文件路径和前面定义的解析处理器。有了具体的XML文件以及对应的处理器来处理对应的标志事情,因此SAX这种解析方式就可以顺利地进行解析工作了
(3)上面测试的输出如下:
SAX解析开始元素'user'的属性是: 属性名:id,属性值: 1 zifangsky 10 male https://www.zifangsky.cn 元素'user'的属性是: 属性名:id,属性值: 2 admin 20 male https://www.tar.pub SAX解析结束 四 JDOM解析XML文件
跟前面两种解析方式不同的是,使用JDOM来解析XML文件需要下载额外的jar包
(1)下载jar包并导入到项目中:
下载地址:http://www.jdom.org/downloads/index.html
目前最新版本是:JDOM 2.0.6
然后将下载得到的“jdom-2.0.6.jar”文件导入到测试项目中
注:关于如何在一个Java项目中导入额外的jar,这里将不多做解释,不太会的童鞋可以自行百度
(2)测试代码:
JDOMTest.java:
package cn.zifangsky.xml; import java.util.List; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.input.SAXBuilder; public class JDOMTest { /** * @param args */ public static void main(String[] args) { SAXBuilder saxBuilder = new SAXBuilder(); try { Document document = saxBuilder.build("src/cn/zifangsky/xml/demo1.xml"); //获取XML文件的根节点 Element rootElement = document.getRootElement(); // System.out.println(rootElement.getName()); ListusersList = rootElement.getChildren(); //获取子节点 for(Element u : usersList){ // List attributes = u.getAttributes(); // for(Attribute attribute : attributes){ // System.out.println("属性名:" + attribute.getName() + ",属性值:" + attribute.getValue()); // } System.out.println("'id'的值是: " + u.getAttributeValue("id")); } }catch (Exception e) { e.printStackTrace(); } } } 从上面的代码可以看出,使用JDOM来解析XML文件,主要需要做以下几个步骤:
新建一个SAXBuilder
通过SAXBuilder的build方法传入一个XML文件的路径得到Document
通过Document的getRootElement方法获取根节点
通过getChildren方法获取根节点的所有子节点
然后是遍历每个子节点,获取属性、属性值、节点名、节点值等内容
如果该节点也有子节点,然后同样可以通过getChildren方法获取该节点的子节点
后面的步骤跟上面一样,不断递归到文本节点截止
(3)上面测试的输出如下:
'id'的值是: 1 'id'的值是: 2五 DOM4J解析XML文件
jar包下载地址:https://sourceforge.net/projects/dom4j/files/
同样,在使用DOM4J解析XML文件时需要往项目中引入“dom4j-1.6.1.jar”文件
(1)一个简单实例:
i)DOM4JTest.java:
package cn.zifangsky.xml; import java.io.File; import java.util.Iterator; import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class DOM4JTest { public static void main(String[] args) { SAXReader reader = new SAXReader(); try { Document document = reader.read(new File("src/cn/zifangsky/xml/demo1.xml")); //获取XML文件的根节点 Element rootElement = document.getRootElement(); System.out.println(rootElement.getName()); //通过elementIterator方法获取迭代器 Iteratoriterator = rootElement.elementIterator(); //遍历 while(iterator.hasNext()){ Element user = iterator.next(); //获取属性并遍历 List aList = user.attributes(); for(Attribute attribute : aList){ System.out.println("属性名:" + attribute.getName() + ",属性值:" + attribute.getValue()); } //子节点 Iterator childList = user.elementIterator(); while(childList.hasNext()){ Element child = childList.next(); // System.out.println(child.getName() + " : " + child.getTextTrim()); System.out.println(child.getName() + " : " + child.getStringValue()); } } } catch (Exception e) { e.printStackTrace(); } } } 从上面的代码可以看出,跟前面的JDOM解析方式流程是差不多的,并且关键地方也有注释,因此这里就不多做解释了
ii)上面的代码输出如下:
employees 属性名:id,属性值:1 name : zifangsky age : 10 sex : male contact : https://www.zifangsky.cn 属性名:id,属性值:2 name : admin age : 20 sex : male contact : https://www.tar.pub(2)将XML文件解析成Java对象:
i)为了方便测试,这里准备一个新的XML文件:
demo2.xml:
zifangsky 100 男 https://www.zifangsky.cn 旺财 九头猫妖 ii)同时准备一个Java实体类,恰好跟上面的XML文件中的属性相对应:
User.java:
package cn.zifangsky.xml; import java.util.List; public class User { private String name; private String sex; private int age; private String contact; private ListownPet; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } protected List getOwnPet() { return ownPet; } protected void setOwnPet(List ownPet) { this.ownPet = ownPet; } @Override public String toString() { return "User [name=" + name + ", sex=" + sex + ", age=" + age + ", contact=" + contact + ", ownPet=" + ownPet + "]"; } } iii)测试代码:
XMLtoJava.java:
package cn.zifangsky.xml; import java.io.File; import java.util.ArrayList; import java.util.List; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class XMLtoJava { public User parseXMLtoJava(String xmlPath){ User user = new User(); ListownPet = new ArrayList (); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(new File(xmlPath)); Element rootElement = document.getRootElement(); //获取根节点 List children = rootElement.elements(); //获取根节点的子节点 //遍历 for(Element child : children){ String elementName = child.getName(); //节点名 String elementValue = child.getStringValue(); //节点值 switch (elementName) { case "name": user.setName(elementValue); break; case "sex": user.setSex(elementValue); break; case "age": user.setAge(Integer.valueOf(elementValue)); break; case "contact": user.setContact(elementValue); break; case "ownPet": ownPet.add(elementValue); break; default: break; } } user.setOwnPet(ownPet); } catch (Exception e) { e.printStackTrace(); } return user; } public static void main(String[] args) { XMLtoJava demo = new XMLtoJava(); User user = demo.parseXMLtoJava("src/cn/zifangsky/xml/demo2.xml"); System.out.println(user); } } 经过前面的分析之后,上面这个代码也是很容易理解的:通过遍历节点,如果节点名跟Java类中的某个属性名相对应,那么就将节点值赋值给该属性
iv)上面的代码输出如下:
User [name=zifangsky, sex=男, age=100, contact=https://www.zifangsky.cn, ownPet=[旺财, 九头猫妖]](3)解析一个XML文件并尽可能原样输出:
DOM4JTest2:
package cn.zifangsky.xml; import java.io.File; import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class DOM4JTest2 { /** * 解析XML文件并尽可能原样输出 * * @param xmlPath * 待解析的XML文件路径 * @return null * */ public void parse(String xmlPath) { SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(new File(xmlPath)); Element rootElement = document.getRootElement(); print(rootElement, 0); } catch (Exception e) { e.printStackTrace(); } } /** * 打印一个XML节点的详情 * * @param element * 一个XML节点 * @param level * 用于判断xml节点前缩进多少的标识,每深入一层则多输出4个空格 * @return null * */ public void print(Element element, int level) { ListelementList = element.elements(); // 当前节点的子节点List // 空格 StringBuffer spacebBuffer = new StringBuffer(""); for (int i = 0; i attributes = element.attributes(); for (Attribute attribute : attributes) System.out.print(" " + attribute.getName() + "=\"" + attribute.getText() + "\""); // 有子节点 if (elementList.size() > 0) { System.out.println(">"); // 遍历并递归 for (Element child : elementList) { print(child, level + 1); } // 输出结束节点 System.out.println(space + "" + element.getName() + ">"); } else { // 如果节点没有文本则简化输出 if (element.getStringValue().trim().equals("")) System.out.println(" />"); else System.out.println(">" + element.getStringValue() + "" + element.getName() + ">"); } } public static void main(String[] args) { DOM4JTest2 test2 = new DOM4JTest2(); test2.parse("src/cn/zifangsky/xml/demo3.xml"); } } 这段代码同样没有什么新的东西,原理就是利用递归来不断进行解析输出,注意一下不同层次的节点的缩进即可。刚开始测试时建议用一些结构比较简单的代码,如上面的demo1.xml和demo2.xml文件。在测试没问题时可以选择一些复杂的XML文件来测试是否能够正常输出,比如:
demo3.xml:
为什么我在标题上说的是尽可能原样输出,其原因就是上面那段解析代码在碰到下面这种XML节点时,输出就不一样了:
这段XML文档节点最后输出如下:
admin 运维 zabbix 端口
推荐阅读
-
java基础 xml 使用dom4j解析 xml文件 servlet根据pattern 找到class
-
Java基础之XML介绍与SAX解析、DOM解析XML、JDOM解析、DOM4J解析、XMLEncoder与XMLDecoder的使用以及xstream工具的使用 189~195
-
Java基础系列17:使用DOM、SAX、JDOM、DOM4J解析XML文件详解
-
java基础 xml 使用dom4j解析 xml文件 servlet根据pattern 找到class
-
Java基础系列17:使用DOM、SAX、JDOM、DOM4J解析XML文件详解
-
Java基础系列17:使用DOM、SAX、JDOM、DOM4J解析XML文件详解