在项目里,我们往往会把一些配置信息放到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文件的几种方式(DOM解析)

    好久不用的东西,今天居然被面试官问到了.那既然这样,我们就一起回顾下java解析xml文件的几种方式吧. DOM解析 dom解析所需依赖是我们jdk自带的,所以只需要使用jdk为我们提供的接口即可上手 ...

  2. java解析xml文件

    使用java解析xml文件,通过dom4j. 代码如下: package com.java.team; import java.io.File; import java.util.ArrayList; ...

  3. XML解析 (JAVA解析xml文件)java+Dom4j+Xpath xml文件解析根据子节点得到父节点 查找校验xml文件中相同的节点属性值 java遍历文件夹解析XML

    XML解析 (JAVA解析xml文件)java+Dom4j+Xpath xml文件解析根据子节点得到父节点 以及查找xml文件中相同的节点属性值 项目背景:这是本人实习中所碰到的项目,当时感觉很棘手, ...

  4. Java解析xml文件dom4j篇(基于xml配置文件完成Excel数据的导入、导出功能完整实现)

    DOM4J解析XML文件 dom4j是一个Java的XML API,是jdom的升级产品,用来读写XML文件.另外对比其他API读写XML文件,dom4j是一个十分优秀的JavaXML API,具有性 ...

  5. 使用java解析XML文件的步骤

    以前的时候,也解析过,今天又拿出来解析就让忘记怎么解析了,后来在网上查还有自己想,终于解析出来了,下面就是原XML文件: accp.xml <?xml version="1.0&quo ...

  6. java解析xml文件:创建、读取、遍历、增删查改、保存

    全栈工程师开发手册 (作者:栾鹏) java教程全解 java使用JDOM接口解析xml文件,包含创建.增删查改.保存,读取等操作. 需要引入jdom.jar,下载 xercesImpl.jar,下载 ...

  7. python 解析xml 文件: SAX方式

    环境 python:3.4.4 准备xml文件 首先新建一个xml文件,countries.xml.内容是在python官网上看到的. <?xml version="1.0" ...

  8. java 解析xml文件案例

    package xmlTest;import javax.xml.parsers.*; import org.w3c.dom.*;public class GetXml {public static ...

  9. java解析xml文件并写入Excel表

    解析xml文件并写入Excel表 类似于如下格式的xml数据,解析之后将数据写入Excel中 <?xml version="1.0" encoding="UTF-8 ...

最新文章

  1. PC端、移动端手机竖拍原图压缩上传顺时针旋转90°的解决方案
  2. 想成为系统高手就进来,就怕你学不完
  3. 20165302第八周总结
  4. Golang中的自动伸缩和自防御设计
  5. oracle中if/else功能的实现的3种写法
  6. c语言p1口转向灯实验,实验三模拟汽车左右转向灯控制.doc
  7. 在微服务中如何管理数据
  8. c语言see函数,vprintf() - C语言库函数
  9. Hbase Compaction 源码分析 - CompactSplitThread 线程池选择
  10. html框架里面怎么填写内容_还不会Python高级框架?进来,速学
  11. ubuntu-18.04 设置开机启动脚本-亲测有效
  12. WPF设置控件获得焦点FocusManager
  13. 文字降重-临逸版-论文降重
  14. 这一年,我“生病”了
  15. centos7上运行 ultravnc repeater
  16. Apache2.4 HTTP服务器配置反向代理和负载均衡
  17. 联想拯救者R7000安装Ubuntu18.04
  18. Android实例之必应每日一图
  19. C++基本数据类型的字节数、范围大小、溢出处理
  20. Python之粒子群算法(含代码实例)

热门文章

  1. mysql给我的愚人节礼物——分组排序进行排名语句存在的问题
  2. Java配置jdk环境变量
  3. 蒸汽机器人布里茨天赋_7.13lol布里茨出装 机器人符文天赋蒸汽机器人攻略
  4. 安装Vue Devtools extension
  5. 中柏ezpad4s_超强大屏平板中柏EZpad 4S双系统评测
  6. 【技能】眼镜清洗,如何不用眼镜布让眼镜一尘不染?
  7. hbase 默认目录_详解HBase目录结构的三大部分
  8. sysctl 默认值_sysctl.conf文件配置详解
  9. C#,幻方(Magic Square)的算法与源代码
  10. 白日门修改超级会员QQ号码