一、概述
SAX,全稱Simple API for XML,是一種以事件驅動的XMl API,是XML解析的一種新的替代方法,解析XML常用的還有DOM解析,PULL解析(Android特有),SAX與DOM不同的是它邊掃描邊解析,自頂向下依次解析,由于邊掃描邊解析,是以它解析XML具有速度快,占用記憶體少的優點,對于Android等CPU資源寶貴的移動平台來說是一個巨大的優勢。
- SAX的優點:
- 解析速度快
- 占用記憶體少
- SAX的缺點:
- 無法知道目前解析标簽(節點)的上層标簽,及其嵌套結構,僅僅知道目前解析的标簽的名字和屬性,要知道其他資訊需要程式猿自己編碼
- 隻能讀取XML,無法修改XML
- 無法随機通路某個标簽(節點)
- SAX解析适用場合
- 對于CPU資源寶貴的裝置,如Android等移動裝置
- 對于隻需從xml讀取資訊而無需修改xml
二、SAX解析的步驟
解析步驟很簡單,可分為以下四個步驟
- 得到xml檔案對應的資源,可以是xml的輸入流,檔案和uri
- 得到SAX解析工廠(
)SAXParserFactory
- 由解析工廠生産一個SAX解析器(
)SAXParser
- 傳入輸入流和handler給解析器,調用
解析parse()
知道了SAX解析的優缺點和解析步驟,下面我們通過一個簡單的Demo學習一下SAX解析XML
三、SAX解析實戰
建立一個Android工程叫SaxParseXmlDemo,将sax.jar下載下傳放到工程的lib下面并添加到建構路徑中,為了友善,我先将工程的目錄結構列一下:
1、建立一個xml檔案叫users.xml
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="1">
<name>畢向東</name>
<password>bxd123</password>
</user>
<user id="2">
<name>韓順平</name>
<password>hsp123</password>
</user>
<user id="3">
<name>馬士兵</name>
<password>msb123</password>
</user>
</users>
2、建立一個JavaBean
package com.example.saxparsexmldemo;
public class User {
private long id;
private String name;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
3、建立一個類XmlParseHandler.java,該類需要繼承
DefaultHandler
或者實作
ContentHandler
接口,這裡我們通過繼承
DefaultHandler
(實作了
ContentHandler
接口)的方式,該類是SAX解析的核心所在,我們要重寫以下幾個我們關心的方法。
- startDocument():文檔解析開始時調用,該方法隻會調用一次
-
startElement(String uri, String localName, String qName,
Attributes attributes):标簽(節點)解析開始時調用
- uri:xml文檔的命名空間
- localName:标簽的名字
- qName:帶命名空間的标簽的名字
- attributes:标簽的屬性集
- characters(char[] ch, int start, int length):解析标簽的内容的時候調用
- ch:目前讀取到的TextNode(文本節點)的位元組數組
- start:位元組開始的位置,為0則讀取全部
- length:目前TextNode的長度
- endElement(String uri, String localName, String qName):标簽(節點)解析結束後調用
- endDocument():文檔解析結束後調用,該方法隻會調用一次
重寫startDocument(),我們在這裡初始化User集合,該集合用來存放解析出來的user
Log.e("startDocument", "startDocument()");
users = new ArrayList<User>();
重寫startElement,在
startElement
中先判斷目前的标簽是否user,如果是user标簽則說明接下來是一個user的資訊,是以我們建立一個User對象用來存放這個user的資訊,在這裡我們得到目前user标簽的id屬性,封裝到user對象中。并記錄目前的标簽
Log.e("startElement", localName + "-startElement()");
if ("user".equals(localName)) { // 是一個使用者
for (int i = ; i < attributes.getLength(); i++) {
Log.e("attributes", "attribute_name:" + attributes.getLocalName(i)
+ " attribute_value:" + attributes.getValue(i));
user = new User();
if("id".equals(attributes.getLocalName(i))){
user.setId(Long.parseLong(attributes.getValue(i)));
}
}
}
currentTag = localName; // 把目前标簽記錄下來
重寫characters,在
characters
中解析出目前标簽的内容,如果目前标簽為name标簽,則解析name标簽的内容封裝到目前User對象的name屬性中,如果目前标簽為password标簽,則解析password标簽的内容封裝到目前User對象的password屬性中
String value = new String(ch,start,length); // 将目前TextNode轉換為String
Log.e("characters", value+"");
if("name".equals(currentTag)){ // 目前标簽為name标簽,該标簽無子标簽,直接将上面擷取到的标簽的值封裝到目前User對象中
// 該節點為name節點
user.setName(value);
}else if("password".equals(currentTag)){ // 目前标簽為password标簽,該标簽無子标簽,直接将上面擷取到的标簽的值封裝到目前User對象中
// 該節點為password節點
user.setPassword(value);
}
重寫endElement,在這個方法中判斷目前是否是user标簽的結束,如果是user标簽結束,則這個user資訊解析結束,并将目前的User對象和目前的标簽重置
Log.e("endElement", localName + "-endElement()");
if("user".equals(localName)){
users.add(user);
user = null;
}
currentTag = null;
重寫endDocument,這裡直接給個空實作,我們隻需觀察Log輸出
Log.e("endDocument", "-endDocument()");
XmlParseHandler.java完整代碼:
package com.example.saxparsexmldemo;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;
public class XmlParseHandler extends DefaultHandler {
private List<User> users;
private String currentTag; // 記錄目前解析到的節點名稱
private User user; // 記錄目前的user
/**
* 文檔解析結束後調用
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
Log.e("endDocument", "-endDocument()");
}
/**
* 節點解析結束後調用
* @param uri : 命名空間的uri
* @param localName : 标簽的名稱
* @param qName : 帶命名空間的标簽名稱
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
super.endElement(uri, localName, qName);
Log.e("endElement", localName + "-endElement()");
if("user".equals(localName)){
users.add(user);
user = null;
}
currentTag = null;
}
/**
* 文檔解析開始調用
*/
@Override
public void startDocument() throws SAXException {
super.startDocument();
Log.e("startDocument", "startDocument()");
users = new ArrayList<User>();
}
/**
* 節點解析開始調用
* @param uri : 命名空間的uri
* @param localName : 标簽的名稱
* @param qName : 帶命名空間的标簽名稱
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
Log.e("startElement", localName + "-startElement()");
if ("user".equals(localName)) { // 是一個使用者
for (int i = ; i < attributes.getLength(); i++) {
Log.e("attributes", "attribute_name:" + attributes.getLocalName(i)
+ " attribute_value:" + attributes.getValue(i));
user = new User();
if("id".equals(attributes.getLocalName(i))){
user.setId(Long.parseLong(attributes.getValue(i)));
}
}
}
currentTag = localName; // 把目前标簽記錄下來
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
String value = new String(ch,start,length); // 将目前TextNode轉換為String
Log.e("characters", value+"");
if("name".equals(currentTag)){ // 目前标簽為name标簽,該标簽無子标簽,直接将上面擷取到的标簽的值封裝到目前User對象中
// 該節點為name節點
user.setName(value);
}else if("password".equals(currentTag)){ // 目前标簽為password标簽,該标簽無子标簽,直接将上面擷取到的标簽的值封裝到目前User對象中
// 該節點為password節點
user.setPassword(value);
}
}
public List<User> getUsers() {
return users;
}
}
4、建立一個類XmlParseUtils.java,寫一個方法進行xml解析
public static List<User> getUsers() throws ParserConfigurationException, SAXException, IOException {
// 加載檔案傳回檔案的輸入流
InputStream is = XmlParseUtils.class.getClassLoader().getResourceAsStream("users.xml");
XmlParseHandler handler = new XmlParseHandler();
// 1. 得到SAX解析工廠
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
// 2. 讓工廠生産一個sax解析器
SAXParser newSAXParser = saxParserFactory.newSAXParser();
// 3. 傳入輸入流和handler,解析
newSAXParser.parse(is, handler);
is.close();
return handler.getUsers();
}
其中第三步也可以通過XMLReader來完成
XMLReader xmlReader = newSAXParser.getXMLReader();
InputSource input = new InputSource(is);
xmlReader.parse(input );
到這裡SAX解析XML的代碼完成了,OK,萬事具備,隻欠測試了
這裡我們用Android的單元測試,隻需完成調用XmlParseUtils的getUsers()方法測試輸出即可
Android的單元測試(需要連接配接模拟器或者手機)
- 環境搭建
- 在 AndroidManifest.xml的根節點下面添加一個
instrumentation
- 在 AndroidManifest.xml的application節點下面添加uses-library
- 在 AndroidManifest.xml的根節點下面添加一個
- 在
下面建立一個測試類SAXParseXmlTest.java,寫一個測試方法com.example.saxparsexmldemo
public void testSAXgetUsers() throws Exception{
List<User> users = XmlParseUtils.getUsers();
for(User user : users){
Log.e(TAG, "name:"+user.getName());
Log.e(TAG, "password:"+user.getPassword());
}
}
OK,右鍵該類的testSAXgetUsers,Run As Android JUnit Test,我們可以看到XML解析成功
通過上面的這個小Demo的完成,我們可以看到SAX解析代碼不多也不難了解,關鍵是Handler的幾個方法的使用,即遇到什麼符号會觸發什麼事件,以及觸發過程,掌握了SAX的事件觸發,那麼就掌握了SAX解析XML,下面我們來分析一下SAX解析的原理或流程
四、SAX解析XML原理
在分析SAX解析原理之前,我們先看一下上面的demo的日志輸出
以users.xml的解析過程為例,結合上面的日志輸出,我們可以分析出SAX解析的流程
1、xml解析開始,
startDocument被調用
,這個方法在整個xml解析過程中調用了一次,是以我們可以在這個方法裡面初為解析XML做一些準備,比如初始化變量
2、解析每遇到一個标簽都會經曆
startElement
-
characters
-
endElement
這個過程,即每一個标簽都會觸發
startElement
-
characters
-
endElement
3、通過users這個根節點的解析,user标簽解析以及name,password标簽解析過程,我們知道user标簽是users的子标簽,name和password标簽是user标簽的子标簽,我們知道當解析一個标簽的時候,如果該标簽有子标簽,則先回調用該标簽的
startElement
方法,這裡面可以先得到該标簽的屬性資訊,然後觸發
characters
解析該标簽的内容(值),然後子标簽觸發
startElement
-
characters
-
endElement(子标簽觸發)
,最後該标簽觸發
endElement
,該标簽解析結束
下面畫一個圖讓我們進一步了解SAX解析XML的原理:
對上圖做個簡單說明,當目前标簽有子标簽的時候,該标簽先觸發
characters
,然後子标簽觸發
startElement
-
characters
-
endElement
事件,這個過程可以了解為一個不斷遞歸的過程。
OK,SAX解析原理分析完了,最後用一句話描述SAX解析過程
startDocument-startElement-characters-endElement-endDocument
總結,SAX解析XML具有解析速度快,占用記憶體少,對于Android等移動裝置來說有巨大的優勢,深入了解SAX的事件觸發機制是掌握SAX解析的關鍵,掌握了SAX的事件觸發就掌握了SAX解析XML
上面這篇文章由于個人了解,如果有了解錯的地方,歡迎大家指出,與君共勉,一起進步。
Demo下載下傳位址:http://download.csdn.net/detail/ydxlt/9328309
下篇文章:【XML解析(二)】DOM解析XML