Android之SAX、DOM和Pull解析XML

Android之SAX、DOM和Pull解析XML

文章鏈接:http://blog.csdn.net/qq_16628781/article/details/70147230

知識點

  1. XML的3種解析方式:SAX、DOM和Pull;
  2. PULL解析XML文檔示例;
  3. Dom解析XML文檔示例;
  4. SAX解析XML文檔示例;
  5. 調用運行結果示例;
  6. 新名詞記錄{SAX、DOM和Pull解析XML文檔}

概述

XML的解析有3中解析方式:SAX、DOM、Pull。

SAX(Simple API for XML) 使用流式處理的方式,它並不記錄所讀內容的相關信息。它是一種以事件爲驅動的XML API,解析速度快,佔用內存少。使用回調函數來實現。 缺點是不能倒退。

DOM(Document Object Model) 是一種用於XML文檔的對象模型,可用於直接訪問XML文檔的各個部分。它是一次性全部將內容加載在內存中,生成一個樹狀結構,它沒有涉及回調和複雜的狀態管理。 缺點是加載大文檔時效率低下。

Pull內置於Android系統中。也是官方解析佈局文件所使用的方式。Pull與SAX有點類似,都提供了類似的事件,如開始元素和結束元素。不同的是,SAX的事件驅動是回調相應方法,需要提供回調的方法,而後在SAX內部自動調用相應的方法。而Pull解析器並沒有強制要求提供觸發的方法。因爲他觸發的事件不是一個方法,而是一個數字。它使用方便,效率高。

SAX、DOM、Pull的比較:
1. 內存佔用:SAX、Pull比DOM要好;
2. 編程方式:SAX採用事件驅動,在相應事件觸發的時候,會調用用戶編好的方法,也即每解析一類XML,就要編寫一個新的適合該類XML的處理類。DOM是W3C的規範,Pull簡潔。
3. 訪問與修改:SAX採用流式解析,DOM隨機訪問。
4. 訪問方式:SAX,Pull解析的方式是同步的,DOM逐字逐句。

下面是每個解析方式的使用方法講解示例。


XML文檔和實體類

首先是XML文檔users.xml

<?xml version="1.0" encoding="UTF-8" ?>
<users>
    <user id="1">
    <userName>yaoj</userName>
    <password>123456</password>
    <age>24</age>
    </user>
<user id="2">
<userName>tanksu</userName>
<password>3333333</password>
<age>23</age>
    </user>
<user id="3">
<userName>fishing</userName>
<password>666666</password>
<age>26</age>
    </user>
</users>

因爲要解析成對象,那麼就必須要有對應的實體類。

public class UserBean implements Serializable {

    //串行化版本統一標識符
    private static final long serialVersionUID = 1L;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    private int id;
    private String userName;
    private transient String password;
    private int age;

    public static class InstanceHolder {
        private static final UserBean userBean = new UserBean("tanksu", "999999", 12);
    }

    private Object readResolve() throws ObjectStreamException {
        return InstanceHolder.userBean;
    }

    public UserBean() {
    }

    public UserBean(String userName, String password, int age) {
        this.userName = userName;
        this.password = password;
        this.age = age;
    }

    //初始化version版本號
    private final long version = 2L;

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        if (version == 1L) {
            out.writeObject(password);
        } else if (version == 2L) {
            out.writeObject(password);
        } else {
            out.writeObject(password);
        }
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        password = (String) in.readObject();
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUserName() {

        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "{" +
                "id:" + id +
                ", userName:" + userName +
                ", password:" + password +
                ", age:" + age +
                ", version:" + version +
                "}";
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

上面的實體類請忽略version字段,因爲這個類是項目中其它用處,如果要解析,那麼直接在對應級別上面加入一個判斷就可以了。


PULL

在Android中,自帶的解析方法就是pull。它的優點在於便捷高效。下面我們直接看代碼:

/**
     * 根據pull解析XML文檔
     *
     * @param inputStream 需要解析的輸入流
     * @return 返回解析後的集合,可能爲空
     */
    public static List<UserBean> parseXmlByPull(InputStream inputStream) {
        XmlPullParser pullParser = Xml.newPullParser();
        List<UserBean> userBeanList = null;
        try {
            pullParser.setInput(inputStream, "UTF-8");
            //START_TAG, END_TAG, TEXT等等的節點
            int eventType = pullParser.getEventType();
            UserBean userBean = null;
            //當還沒有解析到結束文檔的節點,一直循環
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT: //開始解析文檔,最外層的節點<users>
                        userBeanList = new ArrayList<>();
                        break;
                    case XmlPullParser.START_TAG: //開始解析到節點,需要對每一個定義的節點進行處理
                        if ("user".equals(pullParser.getName())) {
                            userBean = new UserBean();
                            int id = Integer.parseInt(pullParser.getAttributeValue(0));
                            userBean.setId(id);
                        } else if ("userName".equals(pullParser.getName())) {
                            if (userBean != null) userBean.setUserName(pullParser.nextText());
                        } else if ("passwor".equals(pullParser.getName())) {
                            if (null != userBean) userBean.setPassword(pullParser.nextText());
                        } else if ("age".equals(pullParser.getName())) {
                            if (null != userBean)
                                userBean.setAge(Integer.parseInt(pullParser.nextText()));
                        }
                        break;
                    case XmlPullParser.END_TAG: //結束節點,將解析的userbean加入到集合中
                        if ("user".equals(pullParser.getName())) {
                            userBeanList.add(userBean);
                            userBean = null;
                        }
                        break;
                    case XmlPullParser.END_DOCUMENT:
                        break;
                }
                //獲取到下一個節點,在觸發解析動作
                eventType = pullParser.next();
            }
        } catch (XmlPullParserException | IOException e) {
            e.printStackTrace();
        }
        return userBeanList;
    }

解釋:首先取得XmlPullParser類的一個實例,然後將輸入流進行解析,解析的結果會放到pullParser裏面,獲取到eventType,這裏的eventType對應的分別有文檔開始(START_DOCUMENT)、文檔結束(END _DOCUMENT)、節點開始(START _TAG)、節點結束(END _TAG)等;然後根據每個不同的節點或者文檔結束,取出不同的數值,設置給
userBean,最後加入到返回的集合裏面去。


Dom

DOM最初在HTML語言裏面認識到,因爲網頁也是使用可擴展語言XML來寫的,最初瀏覽器加載一個網頁的時候,就是將整個網頁都加入到內存裏面進行解析。這樣做的不好就是,可能我只是需要其中某些個數據,但是你都加載到內存了,這就造成了浪費。

廢話不多說,我們直接看如何利用DOM解析XML文檔。

/**
     * 根據dom解析XML文檔
     *
     * @param inputStream 需要解析的輸入流
     * @return 返回解析後的集合,可能爲空
     */
    public static List<UserBean> parseUserByDom(InputStream inputStream) {
        List<UserBean> userBeanList = new ArrayList<>();
        try {
            //生成一個文檔工廠類
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //
            DocumentBuilder builder = factory.newDocumentBuilder();

            //開始解析輸入流,因爲dom解析是首先將整個文檔讀入內存,所以這裏就完成了解析
            //下面是對這個文檔取出操作
            Document document = builder.parse(inputStream);
            //獲取到文檔的根節點<users>
            Element root = document.getDocumentElement();
            //獲取到所有下一級節點<user>集合
            //開始解析<user>節點及其子節點
            NodeList nodeList = root.getElementsByTagName("user");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Element element = (Element) nodeList.item(i);
                //獲取<user>的id屬性
                int id = Integer.parseInt(element.getAttribute("id"));
                UserBean userBean = new UserBean();
                userBean.setId(id);
                //獲取<user>節點下的子節點,並且遍歷判斷,取值
                NodeList childNodes = element.getChildNodes();
                for (int y = 0; y < childNodes.getLength(); y++) {
                    if (childNodes.item(y).getNodeType() == Node.ELEMENT_NODE) {
                        if ("userName".equals(childNodes.item(y).getNodeName())) {
                            String name = childNodes.item(y).getFirstChild().getNodeValue();
                            userBean.setUserName(name);
                        } else if ("password".equals(childNodes.item(y).getNodeName())) {
                            String password = childNodes.item(y).getFirstChild().getNodeValue();
                            userBean.setPassword(password);
                        } else if ("age".equals(childNodes.item(y).getNodeName())) {
                            String age = childNodes.item(y).getFirstChild()
                                    .getNodeValue();
                            userBean.setAge(Integer.parseInt(age));
                        }
                    }
                }
                //當一個<user>節點被解析完,那麼加入和集合中
                userBeanList.add(userBean);
            }
            inputStream.close();
        } catch (ParserConfigurationException | IOException | SAXException e) {
            e.printStackTrace();
        }
        return userBeanList;
    }

解釋:開始新建一個DocumentBuilder實體,進行解析輸入的XML文檔,然後獲取到根節點,再獲取到根節點下面所有的子節點,然後利用循環,對每一個< user>進行解析,包括其下面的各個子節點。如果子節點下面還有節點,那麼也需要element.getChildNodes()方法獲取到子節點的列表,在循環取值。如果還有更深的結構,同理可得。最後將獲取的數值設置給實體,加入到返回集合裏面去即可。


SAX

SAX方法會比較麻煩一點,因爲它需要你繼承DefaultHandler類,然後重寫相對應節點觸發的方法。和pull方法類似,不同的是API幫你分類好,到了那個節點,就會調用哪一個方法,而不用自己去判斷。

看下面的示例:

/**
 * desc:SAX解析XML的處理類
 * <p>
 * author:kuyu.yaojt (tanksu)
 * <p>
 * email:[email protected]
 * <p>
 * blog:http://blog.csdn.net/qq_16628781
 * <p>
 * date:17/4/12
 */

public class SAXUtil extends DefaultHandler {
    //解析的集合
    private List<UserBean> userBeanList = new ArrayList<>();
    //通過此變量,記錄當前標籤的名稱
    private String curTagName;
    UserBean userBean; //記錄當前Person

    /**
     * 提供給外部調用的方法
     *
     * @param inputStream 輸入流
     * @return 解析的實體類集合
     */
    public static List<UserBean> parse(InputStream inputStream) throws Exception {
        //創建SAXParserFactory解析工廠類
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXUtil saxUtil = new SAXUtil();
        //實例化一個SAXParser解析類
        SAXParser saxParser = factory.newSAXParser();
        //開始解析文檔
        //參數2:利用我們定義的handler進行解析輸入的文檔
        saxParser.parse(inputStream, saxUtil);
        return saxUtil.getUserBeanList();
    }

    private SAXUtil() {
    }

    private List<UserBean> getUserBeanList() {
        return userBeanList;
    }

    //開始解析文檔,做初始化工作
    @Override
    public void startDocument() throws SAXException {
        userBeanList = new ArrayList<>();
        super.startDocument();
    }

    //開始解析元素
    @Override
    public void startElement(String uri, String localName, String qName,
                             Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        //uri:命名空間的uri,如果沒有,則爲""
        //localName標籤名
        // fullName帶命名空間的標籤名
        // attribute存放該標籤所有屬性
        //attributes:該元素所對應的的所有屬性
        if ("user".equals(localName)) {
            for (int i = 0; i < attributes.getLength(); i++) {
                userBean = new UserBean();
                //根據index拿到屬性的name,因爲可能不止一個屬性
                String localAttributeName = attributes.getLocalName(i);
                if ("id".equals(localAttributeName)) {
                    //根據屬性名拿到值
                    String id = attributes.getValue(uri, localAttributeName);
                    userBean.setId(Integer.valueOf(id));
                }
            }
        }
        curTagName = localName;
    }

    //解析字符
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        //根據char數組生產一個字符串
        String sData = new String(ch, start, length).trim();
        if ("userName".equals(curTagName)) {
            userBean.setUserName(sData);
        } else if ("password".equals(curTagName)) {
            userBean.setPassword(sData);
        } else if ("age".equals(curTagName)) {
            userBean.setAge(Integer.parseInt(sData));
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if ("user".equals(localName)) {
            //當一個用戶被解析完,加入到集合中
            userBeanList.add(userBean);
            userBean = null;
        }
        curTagName = null;
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }

}

解釋:這裏的邏輯可以參照pull解析,不多講。主要提一下,因爲沒有類中沒有返回當前節點名稱的方法,所以需要自己記錄。上面的類,我私有化了構造方法,並且提供了一個靜態的方法來使用sax解析XML文檔。


解析XML文檔

我們這直接調用上面的幾個方法:

InputStream inputStream = getResources().openRawResource(R.raw.users);
        List<UserBean> userBeanList = ParseUtil.parseXmlByPull(inputStream);
        userBeanList = ParseUtil.parseUserByDom(inputStream);
        try {
            userBeanList = SAXUtil.parse(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (UserBean userBean : userBeanList){
            CommonLog.logInfo("-=-=-=>>", userBean.toString());
        }

因爲都是解析同一個XML文檔,解析出來的結果都是一樣的。所以下面只給出一個結果圖。

這裏寫圖片描述


總結

總的來說,3中解析XML的方法各有優劣。使用方法大同小異,只要理解了XML文檔的結構,使用哪一種方法都不是問題了。但是不推薦使用DOM,因爲他是全部讀入內存來解讀,而且解析的代碼感覺不是很清晰。推薦使用pull或者sax都可以。

以上就是所有內容,如有任何問題,請及時與我聯繫,謝謝。

發佈了135 篇原創文章 · 獲贊 70 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章