天天看點

XML解析(一),SAX解析XML

一、概述

 SAX,全稱Simple API for XML,是一種以事件驅動的XMl API,是XML解析的一種新的替代方法,解析XML常用的還有DOM解析,PULL解析(Android特有),SAX與DOM不同的是它邊掃描邊解析,自頂向下依次解析,由于邊掃描邊解析,是以它解析XML具有速度快,占用記憶體少的優點,對于Android等CPU資源寶貴的移動平台來說是一個巨大的優勢。

  • SAX的優點:
    1. 解析速度快
    2. 占用記憶體少
  • SAX的缺點:
    1. 無法知道目前解析标簽(節點)的上層标簽,及其嵌套結構,僅僅知道目前解析的标簽的名字和屬性,要知道其他資訊需要程式猿自己編碼
    2. 隻能讀取XML,無法修改XML
    3. 無法随機通路某個标簽(節點)
  • SAX解析适用場合
    1. 對于CPU資源寶貴的裝置,如Android等移動裝置
    2. 對于隻需從xml讀取資訊而無需修改xml

二、SAX解析的步驟

解析步驟很簡單,可分為以下四個步驟

  1. 得到xml檔案對應的資源,可以是xml的輸入流,檔案和uri
  2. 得到SAX解析工廠(

    SAXParserFactory

  3. 由解析工廠生産一個SAX解析器(

    SAXParser

  4. 傳入輸入流和handler給解析器,調用

    parse()

    解析

知道了SAX解析的優缺點和解析步驟,下面我們通過一個簡單的Demo學習一下SAX解析XML

三、SAX解析實戰

建立一個Android工程叫SaxParseXmlDemo,将sax.jar下載下傳放到工程的lib下面并添加到建構路徑中,為了友善,我先将工程的目錄結構列一下:

XML解析(一),SAX解析XML

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):标簽(節點)解析開始時調用

    1. uri:xml文檔的命名空間
    2. localName:标簽的名字
    3. qName:帶命名空間的标簽的名字
    4. attributes:标簽的屬性集
  • characters(char[] ch, int start, int length):解析标簽的内容的時候調用
    1. ch:目前讀取到的TextNode(文本節點)的位元組數組
    2. start:位元組開始的位置,為0則讀取全部
    3. 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
  • com.example.saxparsexmldemo

    下面建立一個測試類SAXParseXmlTest.java,寫一個測試方法
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解析成功

XML解析(一),SAX解析XML

通過上面的這個小Demo的完成,我們可以看到SAX解析代碼不多也不難了解,關鍵是Handler的幾個方法的使用,即遇到什麼符号會觸發什麼事件,以及觸發過程,掌握了SAX的事件觸發,那麼就掌握了SAX解析XML,下面我們來分析一下SAX解析的原理或流程

四、SAX解析XML原理

在分析SAX解析原理之前,我們先看一下上面的demo的日志輸出

XML解析(一),SAX解析XML
XML解析(一),SAX解析XML

以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的原理:

XML解析(一),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