在项目里,我们往往会把一些配置信息放到xml文件里,或者各部门间会通过xml文件来交换业务数据,所以有时候我们会遇到“解析xml文件”的需求。一般来讲,有基于DOM树和SAX的两种解析xml文件的方式,在这部分里,将分别给大家演示通过这两种方式解析xml文件的一般步骤。

1 XML的文件格式

    XML是可扩展标记语言(Extensible Markup Language)的缩写,在其中,开始标签和结束标签必须配套地出现,我们来看下book.xml这个例子。

1	<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 <books>
3 <book id="01">
4 <name>Java</name>
5 <price>15</price>
6 <memo>good book</memo>
7 </book>
8 <book id="02">
9 <name>FrameWork</name>
10 <price>20</price>
11 <memo>new book</memo>
12 </book>
13 </books>

整个xml文件是一个文档(document),其中第1行表示文件头,在第2和第13行里,我们能看到配套出现的books标签,从标签头到标签尾的部分那我们称之为元素(element)。

所以我们可以这样说,在books元素里,我们分别于第3到第7行和第8到第12行定义了2个book元素,在每个book元素,比如从第4到第6行,又包含着3个元素,比如第一本书的name元素是<name>Java</name>,它的name元素值是Java。

在第3行里,我们还能看到元素里的属性(attribute),比如这个book元素具有id这个属性,具体id的属性值是01。

2 基于DOM树的解析方式

DOM是Document Object Model(文档对象模型)的缩写,在基于DOM树的解析方式里,解析代码会先把xml文档读到内存里,并整理成DOM树的形式,随后再读取。根据之前部分里给出的book.xml文档,我们可以绘制出如下形式的DOM树。

其中,books属于根(root)结点,也叫根元素,由于它包含着两个book元素,所以第二层是两个book结点,每个book元素包含着3个元素,所以第三层是6个元素。在下面的ParserXmlByDom.java的代码里,我们来看下通过DOM树方式解析book.xml文档的详细步骤。

1	//省略import相关类库的代码
2 public class ParserXmlByDom {
3 public static void main(String[] args) {
4 //创建DOM工厂
5 DocumentBuilderFactory domFactory=DocumentBuilderFactory.newInstance();
6 InputStream input = null;
7 try {
8 //通过DOM工厂获得DOM解析器
9 DocumentBuilder domBuilder=domFactory.newDocumentBuilder();
10 //把XML文档转化为输入流
11 input=new FileInputStream("src/book.xml");
12 //解析XML文档的输入流,得到一个Document
13 Document doc=domBuilder.parse(input);

从第5行到第13行,我们完成了用DOM树解析XML文件的准备工作,具体包括,在第5行里创建了DOM工厂,在第9行通过DOM工厂创建了解析xml文件DocumentBuilder类型对象,在第11行把待解析的xml文件放入到一个InputStream类型的对象里,在第13行通过parse方法把xml文档解析成一个基于DOM树结构的Document类型对象。

14	            //得到XML文档的根节点,只有根节点是Element类型
15 Element root=doc.getDocumentElement();
16 // 得到子节点
17 NodeList books = root.getChildNodes();

整个XML文件包含在第13行定义的doc对象里,在第15行里,我们通过getDocumentElement方法得到了根节点(也就是books节点),在第17行,通过getChildNoes方法得到该books节点下的所有子节点,随后开始解析整个xml文档。

需要说明的是,在解析前,我们会通过观察xml文档来了解其中的元素名和属性名,所以在后继的代码里,我们会针对元素名和属性名进行编程。

18	            if(books!=null){
19 for(int i=0;i<books.getLength();i++){
20 Node book=books.item(i);
21 //获取id属性
22 if(book.getNodeType()==Node.ELEMENT_NODE){
23 String id=book.getAttributes().getNamedItem("id").getNodeValue();
24 System.out.println("id is:" + id);
25 //遍历book下的子节点
26 for(Node node=book.getFirstChild(); node!=null;node=node.getNextSibling()){
27 if(node.getNodeType()==Node.ELEMENT_NODE){
28 //依次读取book里的name,price和memo三个子元素
29 if(node.getNodeName().equals("name")){
30 String name=node.getFirstChild().getNodeValue();
31 System.out.println("name is:" + name);
32 }
33 if(node.getNodeName().equals("price")){
34 String price=node.getFirstChild().getNodeValue();
35 System.out.println("price is:" + price);
36 }
37 if(node.getNodeName().equals("memo")){
38 String memo=node.getFirstChild().getNodeValue();
39 System.out.println("memo is:" + memo);
40 }
41 }
42 }
43 }
44 }
45 }

第19行的for循环里,我们是遍历book元素通过观察xml文件,我们发现book元素出现了2次,所有这个循环会运行两次,而且,book元素有1个id属性,所有我们需要通过第23行的代码,得到id属性的值。

在文档里,book元素有3个子节点,分别是name,price和memo,所以在代码的26行里,再次使用for循环遍历其中的子节点。在遍历时,我们通过29到32行的代码获取到了book元素里name的值,通过类似的代码后继的33到40行代码里得到了price和memo这两个元素的值。

46	        } catch (ParserConfigurationException e) {
47 e.printStackTrace();
48 } catch (FileNotFoundException e) {
49 e.printStackTrace();
50 } catch (IOException e) {
51 e.printStackTrace();
52 } catch (SAXException e) {
53 e.printStackTrace();
54 } catch (Exception e) {
55 e.printStackTrace();
56 }
57 //在finally里关闭io流
58 finally{
59 try {
60 input.close();
61 } catch (IOException e) {
62 e.printStackTrace();
63 }
64 }
65 }
66 }

同样地,在解析完成后,在finally从句里,我们关闭了之前用到的IO流(input对象)。

3 基于事件的解析方式

SAX是Simple API for XML的缩写,不同于DOM的文档驱动,它是事件驱动的,也就是说,它是一种基于回调(callback)函数的解析方式,比如开始解析xml文档时,会调用我们自己定义的startDocument函数,从下表里,我们能看到基于SAX方式里的各种回调函数以及它们被调用的时间点。

函数名

调用时间点

startDocument

开始解析xml文档时(解析xml文档第一个字符时)会被调用

endDocument

当解析完xml文档时(解析到xml文档最后一个字符时)会被调用

startElement

当解析到开始标签时会被调用,比如在解析“<name>FrameWork</name>”这个element时,当读到开始标签“<name>”时,会被调用

endElement

当解析到结束标签时会被调用,比如在解析“<name>FrameWork</name>”这个element时,当读到结束标签“</name>”时,会被调用

characters

1行开始后,遇到开始或结束标签之前存在字符,则会调用

2两个标签之间,存在字符,则会调用,比如在解析“<name>FrameWork</name>”时,发现存在FrameWork,则会被调用

3标签和行结束符之前存在字符,则会调用

从上表里我们能看到characters方法会在多个场合被回调,但我们最期望的调用场景是第2种,这就要求我们最好在解析xml文档前整理下它的格式,尽量避免第1和第3种情况。在ParserXmlBySAX.java这个案例中,我们通过了编写上述的回调函数,实现了SAX方式解析xml文档的功能。

1	//省略import的代码
2 //基于SAX的解析代码需要继承DefaultHandler类
3 public class ParserXmlBySAX extends DefaultHandler{
4 // 记录当前解析到的节点名
5 private String tagName;
6 //主方法
7 public static void main(String[] argv) {
8 String uri = "src/book.xml";
9 try {
10 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
11 ParserXmlBySAX myParser = new ParserXmlBySAX();
12 SAXParser parser = parserFactory.newSAXParser();
13 parser.parse(uri, myParser);
14 } catch (IOException ex) {
15 ex.printStackTrace();
16 } catch (SAXException ex) {
17 ex.printStackTrace();
18 } catch (ParserConfigurationException ex) {
19 ex.printStackTrace();
20 } catch (FactoryConfigurationError ex) {
21 ex.printStackTrace();
22 }
23 }

在main方法的第8行里,我们指定了待解析xml文档的路径和文件名,在第10行里,我们创建了SAXParserFactory这个类型的SAX解析工厂对象。在第12行,我们通过SAX解析工厂对象,创建了SAXParser这个类型的解析类。在第13行,通过了parse方法启动了解析。

在上文里我们就已经知道,在SAX的方式里,是通过调用各种回调函数来完成解析的,所以在代码里,我们还得自定义各个回调函数,代码如下。

// 处理到文档结尾时,直接输出,不做任何动作
25 public void endDocument() throws SAXException {
26 System.out.println("endDocument");
27 }
28 // 处理到结束标签时,把记录当前标签名的tagName设置成null
29 public void endElement(String uri, String localName, String qName) throws SAXException {
30 tagName = null;
31 }
32 // 开始处理文档时,直接输出,不做任何动作
33 public void startDocument() throws SAXException {
34 System.out.println("startDocument");
35 }
36 // 处理开始标签
37 public void startElement(String uri, String localName, String name,Attributes attributes) throws SAXException {
38 if ("book".equals(name)) { //解析book标签的属性
39 for (int i = 0; i < attributes.getLength(); i++) {
40 System.out.println("attribute name is:" + attributes.getLocalName(i) + " attribute value:" + attributes.getValue(i));
41 }
42 }
43 //把当前标签的名字记录到tagName这个变量里
44 tagName = name;
45 }
46 //通过这个方法解析book的三个子元素的值
47 public void characters(char[] ch, int start, int length)
48 throws SAXException {
49 if(this.tagName!=null){
50 String val=new String(ch,start,length);
51 //如果是name,price或memo,则输出它们的值
52 if("name".equals(tagName))
53 { System.out.println("name is:" + val); }
54 if("price".equals(tagName))
55 { System.out.println("price is:" + val); }
56 if("memo".equals(tagName))
57 { System.out.println("memo is:" + val); }
58 }
59 }
60 }

我们用tagName来保存当前的标签名,是为了解析book元素的name,price和memo这三个子元素。

<name>FrameWork</name>

比如当解析到name这个开始标签时,在第44行里,startElement会把tagname值设置成name,当解析到FramWork时,由于它包含在两个标签之间,所以会被触发第47行的characters方法,在其中的第52行的if判断里,由于得知当前的标签名是name,所以会输出FrameWork这个name元素的值,当解析到</name>这个结束标签时,会触发第29行的endElement方法,在其中的30行里,会把tagName值清空。

这段代码的输出结果如下,其中第1行和第10行分别是在开始解析和完成解析时输出的。

第2行针对id属性的输出是在startElement方法的第40行里被打印的,第3到第5行针对3个book子元素的输出是在characters方法里被打印的。

第2到第5行是针对第一个book元素的输出,而第6到第9行是针对第2个book。

1	startDocument
2 attribute name is:id attribute value:01
3 name is:Java
4 price is:15
5 memo is:good book
6 attribute name is:id attribute value:02
7 name is:FrameWork
8 price is:20
9 memo is:new book
10 endDocument

  

4 DOM和SAX两种解析方式的应用场景

在基于DOM的方式里,由于我们会把整个xml文档以DOM树的方式装载到内存里,所以可以边解析边修改,而且还能再次解析已经被解析过的内容。

而在SAX的方式里,由于我们是以基于回调函数的方式来解析,所以并不需要把整个文档载入到内存,这样能节省内存资源。

所以说,选择 DOM 还是 SAX,这取决于如下三个个因素。

第一,如果我们在解析时还打算更新xml里的数据,那么建议使用DOM方式。

第二,如果待解析的文件过大,把它全部装载到内存时可能会影响到内存性能,那么建议使用SAX的方式。

第三,如果我们对解析的速度有一定的要求,那么建议使用SAX方式,因为它比DOM方式要快些。

Java解析XML文件的方式的更多相关文章

  1. 使用Java解析XML文件或XML字符串的例子

    转: 使用Java解析XML文件或XML字符串的例子 2017年09月16日 11:36:18 inter_peng 阅读数:4561 标签: JavaXML-Parserdom4j 更多 个人分类: ...

  2. JAVA解析XML之SAX方式

    JAVA解析XML之SAX方式 SAX解析xml步骤 通过SAXParseFactory的静态newInstance()方法获取SAXParserFactory实例factory 通过SAXParse ...

  3. JAVA解析XML之DOM方式

    JAVA解析XML之DOM方式 准备工作 创建DocumentBuilderFactory对象;    创建DocumentBuilder对象; 通过DocumentBuilder对象的parse方法 ...

  4. java解析xml文件并输出

    使用java解析xml文件,通过dom4j,代码运行前需先导入dom4j架包. ParseXml类代码如下: import java.io.File; import java.util.ArrayLi ...

  5. android解析xml文件的方式

    android解析xml文件的方式   作者:东子哥 ,发布于2012-11-26,来源:博客园   在androd手机中处理xml数据时很常见的事情,通常在不同平台传输数据的时候,我们就可能使用xm ...

  6. Java解析xml文件遇到特殊符号&会出现异常的解决方案

    文/朱季谦 在一次Java解析xml文件的开发过程中,使用SAX解析时,出现了这样一个异常信息: Error on line 60 of document : 对实体 "xxx" ...

  7. java解析XML文件

    dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的.dom4j是一个非常非常优秀的Java XML API,具有性能优异.功能强大和极端易用使用的特点,同时它也是一个开放源 ...

  8. JAVA解析XML文件(DOM,SAX,JDOM,DOM4j附代码实现)

    1.解析XML主要有四种方式 1.DOM方式解析XML(与平台无关,JAVA提供,一次性加载XML文件内容,形成树结构,不适用于大文件) 2.SAX方式解析XML(基于事件驱动,逐条解析,适用于只处理 ...

  9. 关于java解析xml文件出现的问题

    DOM解析xml文件 问题1:导入javax.xml.parsers.DocumentBuilderFactory出现问题,如图: 解决办法是:由于创建工程时有个默认的jre,重新创建工程改掉就解决了 ...

随机推荐

  1. k8s数据持久化实验

    Step 1:创建PV ============================================ apiVersion: v1kind: PersistentVolumemetadat ...

  2. 解决win10台式机插入耳机没有声音或者音量不大

    没有声音 如果是插入机箱的前面板,尝试切换到后面板,因为有些组装机箱的前面板是不能用的 有声音,单音量不大(注意:并不一定适用于所有情况,但是可以尝试一下) 原因:缺少驱动,可以确定下控制面板是否有这 ...

  3. 微信小程序获取多个input和textarea的值(es6加微信小程序APi)

    wxml js 俩行即可解决,money1,money2,money3

  4. 泛型 List转换成DataTable

    private DataTable listToDataTable<T>(List<T> ListItem) { //实列化DataTable对象 var dt = new D ...

  5. Java中的循环结构

    1.while循环结构 语法: while(循环条件){ //循环操作 } while循环结构流程图: 举例: int i = 1; while(i <= 100){ System.out.pr ...

  6. 0015 行高那些事:line-height

    目标 理解 能说出 行高 和 高度 三种关系 能简单理解为什么行高等于高度单行文字会垂直居中 应用 使用行高实现单行文字垂直居中 能会测量行高 3.1 行高测量 行高的测量方法: 3.2 单行文本垂直 ...

  7. nginx和keeplive实现负载均衡高可用

    一. Keeplive服务介绍 Keeplive期初是专门为LVS设计的,专门用来监控LVS集群系统中各个服务节点的状态,后来又加入VRRP的功能,因此除了配合LVS服务以外,也可以作为其他服务(ng ...

  8. cmake安装mysql及多实例配置方法

    一.安装mysql 1. 生产环境如何选择MySQL版本 1. 选择社区版的稳定GA版本2. 可以选择5.1或5.5.互联网公司主流5.5, 其次是5.1和5.63. 选择发布后6个月以上的GA版4. ...

  9. 「学习笔记」珂朵莉树 ODT

    珂朵莉树,也叫ODT(Old Driver Tree 老司机树) 从前有一天,珂朵莉出现了... 然后有一天,珂朵莉树出现了... 看看图片的地址 Codeforces可还行) 没错,珂朵莉树来自Co ...

  10. 12款好用的Visual Studio插件,最后一款良心推荐

    目录 01 CodeMaid 02 Markdown Editor 03 ReSharper 04 GitHub Extension for Visual Studio 05 ZenCoding 06 ...