SAX解析XML文件采用事件驱动的方式进行,也就是说,SAX是逐行扫描文件,遇到符合条件的设定条件后就会触发特定的事件,回调你写好的事件处理程序。使用SAX的优势在于其解析速度较快,占用内存较少(相对于DOM而言)。而且SAX在解析文件的过程中得到自己需要的信息后可以随时终止解析,并不一定要等文件全部解析完毕。凡事有利必有弊,其劣势在于SAX采用的是流式处理方式,当遇到某个标签的时候,它并不会记录下以前所遇到的标签,也就是说,在处理某个标签的时候,比如在 startElement方法中,所能够得到的信息就是标签的名字和属性,至于标签内部的嵌套结构,上层标签、下层标签以及其兄弟节点的名称等等与其结构相关的信息都是不得而知的。实际上就是把XML文件的结构信息丢掉了,如果需要得到这些信息的话,只能你自己在程序里进行处理了。所以相对DOM而言,SAX处理XML文档没有DOM方便,SAX处理的过程相对DOM而言也比较复杂。
为了说明sax解析xml的过程,此xml文件非标准规范的xml文档
books.xml的内容:
<?xml version='1.0' encoding='UTF-8'?> <books>---books--- <book id="12">---book--- <name>thinking in java</name>---/name--- <price>85.5</price>---/price--- </book>---/book--- <book id="15">---book2--- <name>Spring in Action</name>---/name2--- <price>39.0</price>---/price--- </book>---/book2--- </books>---/books---
Java Sax解析是按照xml文件的顺序一步一步的来解析,在解析xml文件之前,我们要先了解xml文件的节点的种类,一种是ElementNode,一种是TextNode。如上面的这段books.xml。
其中,像<books>、<book>这种节点就属于ElementNode,而thinking in java、85.5这种就属于TextNode。还有一种特殊情况:---book--- 、---/name--- 、---/book---等都是TextNode,sax是基于事件解析xml的那么遇到这些文本节点的时候也会触发characters( )方法。(这也是books.xml写成不规范的原因,方便去理解sax解析的过程)。
既然sax是基于事件解析xml文档,那么哪些xml包含什么呢?
<?xml version='1.0' encoding='UTF-8'?> 这个代表什么?不就是文档开始部分吗, sax就把它作为一个事件 startDocument() ,你重写这个方法就行了。
<book id="12"> 遇到元素节点的开始标签要不要作为一个事件? 当然! startElement() ,现在你会想到结束标签呢?同样 endElement() .....
那么接下来看看这段文字:
startDocument()
当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。
endDocument()
和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。
startElement(String uri, String localName, String qName, Attributes atts)
当读到一个开始标签的时候,会触发这个方法。uri是命名空间(通过xmlns声明),localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。通过atts可以得到所有的属性名和相应的值。注意,如果没有指定Namespace,则qName可能为空,当然不同的SAX实现会有所不同,比如在Android中qName为空,而J2SE中localName为空,所以想要总是得到标签名,就需要检查这两个参数的值了。
endElement(String uri, String localName, String name)
这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。
characters(char[] ch, int start, int length)
这个方法用来处理在XML文件中读到的内容,第一个参数为文件的字符串内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就可以获取内容。
我们只要继承DefaultHandler 重写我们需要的方法就行了。
SaxParseHandler.java 让sax解析器遇到相应事件时做相应的事情:
package com.aib.sax; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SaxParseHandler extends DefaultHandler { /*** * list 组装数据 Book 封装book信息的javabean currentTag * 当前标签,在characters()方法中判断是否为我们处理文本信息所在的标签位置 */ private List<Book> list; private Book book; @Override public String toString() { return "SaxParseHandler [list=" + list + "]"; } public List<Book> getList() { return list; } private String currentTag; @Override public void characters(char[] ch, int start, int length) throws SAXException { // 输出ch,用于测试... System.out.println("characters方法中currentTag=" + currentTag); System.out.println("characters方法中解析到的文本为:" + new String(ch,start,length)+"---结束标记---"); if (currentTag != null ) { if (currentTag.equals("name")) { book.setBookName(new String(ch, start, length)); } else if (currentTag.equals("price")) { book.setPrice(new String(ch, start, length)); } } } @Override public void startDocument() throws SAXException { // 根据这个信息: <?xml version="1.0" encoding="UTF-8"?> 识别 System.out.println("startDocument方法中currentTag=" + currentTag); // 已经开始读取文档,所以初始化list用于保存接下来读取的book信息。注意:此方法调用一次! list = new ArrayList<Book>(); } @Override public void endDocument() throws SAXException { System.out.println("endDocument方法中currentTag=" + currentTag); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 将正在解析元素节点的名称赋值给currentTag,在characters()中可以准确处理文本信息 this.currentTag = qName; // 打印参数信息,用于测试.. System.out.println("startElement方法中currentTag=" + currentTag); if ("book".equals(qName)) { book = new Book(); book.setId(attributes.getValue("id")); } else if ("其他你要处理的elementNode".equals(qName)) { // 本程序为了方便演示,xml文档只提取book节点的信息 } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if ("book".equals(qName)) { list.add(book); book = null; } else if ("其他节点".equals(qName)) { // } System.out.println("endElement方法中currentTag=" + currentTag); this.currentTag = null; } }
再写一个测试类测试我们的代码:
package com.aib.sax; import java.io.InputStream; import java.util.List; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; public class Test { public static void main(String[] args) throws Exception { // 通过工厂方式获取 SAXParser SAXParser parse = SAXParserFactory.newInstance().newSAXParser(); // sax是基于事件解析xml ,需要程序员编写如何去处理xml文件 。 SaxParseHandler saxHandler = new SaxParseHandler(); // 通过类加载器加载xml文件,返回一个InputStream,实际应用中可以通过不同的方式加载,比如解析网络的xml文件 InputStream is = Test.class.getClassLoader().getResourceAsStream( "books.xml"); parse.parse(is, saxHandler); List<Book> books = saxHandler.getList(); System.out.println(" "); for (Book book : books) { System.out.println("id=" + book.getId() + " name=" + book.getBookName() + " price="+book.getPrice()); } } }
这是输出的结果:
1 startDocument方法中currentTag=null 2 startElement方法中currentTag=books 3 characters方法中currentTag=books 4 characters方法中解析到的文本为:---book--- 5 ---结束标记--- 6 startElement方法中currentTag=book 7 characters方法中currentTag=book 8 characters方法中解析到的文本为:---book--- 9 ---结束标记--- 10 startElement方法中currentTag=name 11 characters方法中currentTag=name 12 characters方法中解析到的文本为:thinking in java---结束标记--- 13 endElement方法中currentTag=name 14 characters方法中currentTag=null 15 characters方法中解析到的文本为:---/name--- 16 ---结束标记--- 17 startElement方法中currentTag=price 18 characters方法中currentTag=price 19 characters方法中解析到的文本为:85.5---结束标记--- 20 endElement方法中currentTag=price 21 characters方法中currentTag=null 22 characters方法中解析到的文本为:---/price--- 23 ---结束标记--- 24 endElement方法中currentTag=null 25 characters方法中currentTag=null 26 characters方法中解析到的文本为:---/book--- 27 ---结束标记--- 28 startElement方法中currentTag=book 29 characters方法中currentTag=book 30 characters方法中解析到的文本为:---book2--- 31 ---结束标记--- 32 startElement方法中currentTag=name 33 characters方法中currentTag=name 34 characters方法中解析到的文本为:Spring in Action---结束标记--- 35 endElement方法中currentTag=name 36 characters方法中currentTag=null 37 characters方法中解析到的文本为:---/name2--- 38 ---结束标记--- 39 startElement方法中currentTag=price 40 characters方法中currentTag=price 41 characters方法中解析到的文本为:39.0---结束标记--- 42 endElement方法中currentTag=price 43 characters方法中currentTag=null 44 characters方法中解析到的文本为:---/price--- 45 ---结束标记--- 46 endElement方法中currentTag=null 47 characters方法中currentTag=null 48 characters方法中解析到的文本为:---/book2--- 49 ---结束标记--- 50 endElement方法中currentTag=null 51 endDocument方法中currentTag=null 52 53 54 55 id=12 name=thinking in java price=85.5 56 id=15 name=Spring in Action price=39.0
很清楚的看到
---books---
---/name---
---/book2---等这些内容会调用characters()方法, 说明这也是一个文本节点。
现在我们把books.xml文档修改成如下:
1 <?xml version='1.0' encoding='UTF-8'?> 2 <books><book id="12"><name>thinking in java</name><price>85.5</price></book><book id="15"><name>Spring in Action</name><price>39.0</price></book></books>
程序运行的结果就变成了:
1 startDocument方法中currentTag=null 2 startElement方法中currentTag=books 3 startElement方法中currentTag=book 4 startElement方法中currentTag=name 5 characters方法中currentTag=name 6 characters方法中解析到的文本为:thinking in java---结束标记--- 7 endElement方法中currentTag=name 8 startElement方法中currentTag=price 9 characters方法中currentTag=price 10 characters方法中解析到的文本为:85.5---结束标记--- 11 endElement方法中currentTag=price 12 endElement方法中currentTag=null 13 startElement方法中currentTag=book 14 startElement方法中currentTag=name 15 characters方法中currentTag=name 16 characters方法中解析到的文本为:Spring in Action---结束标记--- 17 endElement方法中currentTag=name 18 startElement方法中currentTag=price 19 characters方法中currentTag=price 20 characters方法中解析到的文本为:39.0---结束标记--- 21 endElement方法中currentTag=price 22 endElement方法中currentTag=null 23 endElement方法中currentTag=null 24 endDocument方法中currentTag=null 25 26 27 28 id=12 name=thinking in java price=85.5 29 id=15 name=Spring in Action price=39.0
我们可以看到<books><book id="12"> 两个标签之间没有内容(空格、换行符也算内容)时不会触发characters()方法。
特别注意:
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- if("book".equals(qName)){
- books.add(book);
- book = null;
- }
- currentTag= null;/**当解析结束时置为空。这里很重要,例如遇到</book>---/book--- <book id="15">,会调用这个方法
- ,如果这里不把currentTag置为null,根据startElement(....)方法,currentTag的值还是price,因为两个标签之间有内容(---/book---),会执行characters(char[] ch, int start, int length)这个方法,而characters(....)方
- 法判断currentTag!=null,会执行if判断的代码,这样就会把空值赋值给book(因为执行了book=null,此时会有空指针异常),这不是我们想要的。*/
- }