XML解析器介绍
Android中提供了三种方式来解析XML:
- SAX(simple API for XML)
- DOM(文档对象模型)
- 以及Android内部使用的Pull解析。
SAX(simple API for XML)解析器
SAX(simple API for XML)是一种XML解析的替代方法。相比于DOM,SAX是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。而且相比于DOM,SAX可以在解析文档的任意时刻停止解析,但任何事物都有其相反的一面,对于SAX来说就是操作复杂。
SAX是事件驱动型的,SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
SAX解析器的优点
- 解析速度快
- 占用内存少
SAX解析器非常适合在Android移动设备中使用。
SAX解析器的缺点
- 只能读取XML,无法修改。
- 无法知道当前解析标签的上层标签及嵌套结构,仅仅知道当前解析的标签的名字和属性,要知道其他信息需要自己实现。
- 无法随机访问某个标签。
DOM解析器
DOM(Document Object Model,文档对象模型)是W3C制定的一套规范标准,即规定了解析文件的接口。各种语言可以按照DOM规范去实现这些接口,给出解析文件的解析器。
DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。
DOM解析器的优点
- 易用性强,使用简单
- DOM在内存中以树形结构存放,因此检索和更新效率会更高
- 适于修改XML结构
DOM解析器的缺点
- 效率低
- 解析速度慢
- 内存占用量过高
- 不适合大型文件
PULL解析器
PULL解析器是一个开源的java项目,既可以用在Android上,也可以用在java EE上,运行方式与SAX相似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。
PULL解析器的优点
- 小巧轻便
- 解析速度快
- 简单易用
- 非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器
PULL解析器的缺点
同SAX。
实战
我们将分别使用三种XML解析器来解析一个XML文件。
将要解析的XML文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<name>android</name>
</book>
<book id="2">
<name>java</name>
</book>
<book id="3">
<name>c++</name>
</book>
</books
SAX解析
public class SAXParserDemo {
public List<Book> getBooks(InputStream instream) {
try {
//创建SAX解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//创建SAX解析器
SAXParser paser = factory.newSAXParser();
//设置解析器的相关特性,true表示开启命名空间特性
paser.setProperty("http://xml.org/sax/features/namespaces",true);
//创建事件处理程序
BookPaser bookPaser = new BookPaser();
//开始解析
paser.parse(instream, bookPaser);
//关闭输入流
instream.close();
//返回解析后的内容
return bookPaser.getBooks();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//创建事件处理程序,也就是编写ContentHandler的实现类,一般继承自DefaultHandler类
public final class BookPaser extends DefaultHandler {
public List<Book> getBooks() {
return books;
}
private List<Book> books = null;
//当前解析的元素标签
private String tagName = null;
private Book Book = null;
//遇到文档开始标记的时候创建Book集合
@Override
public void startDocument () throws SAXException{
books = new ArrayList<Book>();
}
//遇到元素节点开始时候的处理方法
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
tagName = localName;
//如果遇到<Book>标记,则创建一个Book实例
if ("Book".equals(tagName)) {
Book = new Book();
//取出标记内的属性
Book.setId(new Integer(attributes.getValue(0)));
}
}
//遇到文本节点时的操作
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
//文本节点必须前面要有元素节点开始标记
if (tagName != null) {
//取出文本节点的值
String data = new String(ch, start, length);
//如果前面的元素节点开始标记是name
if ("name".equals(tagName)) {
//则将文本节点的值赋值给Book的name
Book.setName(data);
}
}
}
//遇到元素节点结束时候的操作
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
//如果遇到</Book>标记
if ("Book".equals(localName)) {
//则将创建完成的Book加入到集合中去
books.add(Book);
Book = null;//置空下一个Book
}
//置空已有标记,因为要解析下一个节点了
tagName = null;
}
}
}
代码逻辑都包含在注释里了,SAX解析器是通过注册回调的方式实现解析逻辑的,我们的解析逻辑在DefaultHandler类型的子类中实现。
DOM解析
public class DomParserDemo {
public List<Book> getBooks(InputStream instream) throws Exception {
List<Book> books = new ArrayList<Book>();
//创建DOM解析工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//创建DON解析器
DocumentBuilder dombuild = factory.newDocumentBuilder();
//开始解析XML文档并且得到整个文档的对象模型
Document dom = dombuild.parse(instream);
//得到根节点<books>
Element root = dom.getDocumentElement();
//得到根节点下所有标签为<book>的子节点
NodeList bookList = root.getElementsByTagName("book");
//遍历book节点
for (int i = 0; i < bookList.getLength(); i++) {
//首先创建一个Book
Book book = new Book();
//得到本次Book元素节点
Element bookElement = (Element) bookList.item(i);
//得到Book节点中的ID
book.setId(new Integer(bookElement.getAttribute("id")));
//得到Book节点下的所有子节点
NodeList bookChilds = bookElement.getChildNodes();
//遍历Book节点下的所有子节点
for (int j = 0; j < bookChilds.getLength(); j++) {
//如果是元素节点的话
if (bookChilds.item(j).getNodeType() == Node.ELEMENT_NODE) {
//得到该元素节点
Element childElement = (Element) bookChilds.item(j);
//如果该元素节点是name节点
if ("name".equals(childElement.getNodeName())) {
//得到name节点下的第一个文本子节点的值
book.setName(childElement.getFirstChild().getNodeValue());
}
}
}
books.add(book);
}
return books;
}
}
DOM的解析逻辑见注释。
PULL解析
public class PullParserDemo {
public List<Book> getBooks(InputStream instream) throws Exception {
List<Book> books = null;
//当前处理对象
Book book = null;
//得到Pull解析器
XmlPullParser parser = Xml.newPullParser();
//设置输入流的编码
parser.setInput(instream, "UTF-8");
//得到第一个事件类型
int eventType = parser.getEventType();
//如果事件类型不是文档结束的话则不断处理事件
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
//如果是文档开始事件
case (XmlPullParser.START_DOCUMENT):
//初始化Book集合
books = new ArrayList<Book>();
break;
//如果遇到标签开始
case (XmlPullParser.START_TAG):
//获得解析器当前元素的名称
String tagName = parser.getName();
//如果当前标签名称是<book>
if ("book".equals(tagName)) {
//创建一个book
book = new Book();
//将元素的属性值赋值给id
book.setId(new Integer(parser.getAttributeValue(0)));
}
//如果book已经创建完成
if (book != null) {
//如果当前节点标记是name
if ("name".equals(tagName)) {
book.setName(new String(parser.nextText()));
}
}
break;
//如果遇到标签结束
case (XmlPullParser.END_TAG):
//如果是book标签结束
if ("book".equals(parser.getName())) {
//将book加入集合
books.add(book);
//置空
book = null;
}
break;
}
//进入下一个事件处理
eventType = parser.next();
}
return books;
}
}
PULL解析是主动获取事件的,并且下一次事件的处理也由我们来调用触发。
至此,三种解析器的实例Demo已经完成,其实都不算复杂,按照Demo使用即可。
总结
本文分析了Android中,可使用的三种XML解析器,并对它们的实现逻辑及优缺点进行了分析和对比。
我们在实战部分,分别用三种解析器实现了Demo中XML文件的解析,代码注释详细的介绍了整个过程。
在Android中,推荐使用PULL解析器来解析XML文件,并且Android系统中,使用的XML解析器也是PULL解析器。