1. 程式人生 > >_044_Android_解析XML的幾種方式的原理與特點:DOM、SAX、PULL

_044_Android_解析XML的幾種方式的原理與特點:DOM、SAX、PULL

轉自https://blog.csdn.net/tianhouquan/article/details/82467820 ,感謝作者的無私分享。 

解析XML的幾種方式的原理與特點:DOM、SAX、PULL。

XML的解析方式有很多,光開源的就有十多種:如Xerces、JDOM、DOM4J、XOM、JiBX、KXML、XMLBeans、jConfig、XStream、XJR等。

但是最常用的還是sax、dom、pull、dom4j

而android中用的比較多的是 sax(Simple APIs for XML)、dom(Document Object Model)、pull,其中pull在這三個中又最為適用。(大部分用於java的解析器在android上都可以用,對於有人說dom4j最好,這個沒試驗過,暫時不好說,但是大部分人都說pull好)

SAX

sax是一個用於處理xml事件驅動的“推”模型;

優點:解析速度快,佔用記憶體少,它需要哪些資料再載入和解析哪些內容。

缺點:它不會記錄標籤的關係,而是需要應用程式自己處理,這樣就會增加程式的負擔。

DOM

dom是一種文件物件模型;

優點:dom可以以一種獨立於平臺和語言的方式訪問和修改一個文件的內容和結構,dom技術使得使用者頁面可以動態的變化,如動態顯示隱藏一個元素,改變它的屬性,增加一個元素等,dom可以使頁面的互動性大大增強。

缺點:dom解析xml檔案時會將xml檔案的所有內容以文件樹方式存放在記憶體中。

PULL

pull和sax很相似,區別在於:pull讀取xml檔案後觸發相應的事件呼叫方法返回的是數字,且pull可以在程式中控制,想解析到哪裡就可以停止解析。 (SAX解析器的工作方式是自動將事件推入事件處理器進行處理,因此你不能控制事件的處理主動結束;而Pull解析器的工作方式為允許你的應用程式程式碼主動從解析器中獲取事件,正因為是主動獲取事件,因此可以在滿足了需要的條件後不再獲取事件,結束解析。pull是一個while迴圈,隨時可以跳出,而sax不是,sax是隻要解析了,就必須解析完成。)

1)DOM4J效能最好,連Sun的JAXM也在用DOM4J.目前許多開源專案中大量採用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J來讀取XML配置檔案。如果不考慮可移植性,那就採用DOM4J.    

2)JDOM和DOM在效能測試時表現不佳,在測試10M文件時記憶體溢位。在小文件情況下還值得考慮使用DOM和JDOM.雖然JDOM的開發者已經說明他們期望在正式發行版前專注效能問題,但是從效能觀點來看,它確實沒有值得推薦之處。另外,DOM仍是一個非常好的選擇。DOM實現廣泛應用於多種程式語言。它還是許多其它與XML相關的標準的基礎,因為它正式獲得W3C推薦(與基於非標準的Java模型相對),所以在某些型別的專案中可能也需要它(如在JavaScript中使用DOM)。   

3)SAX表現較好,這要依賴於它特定的解析方式-事件驅動。一個SAX檢測即將到來的XML流,但並沒有載入到記憶體(當然當XML流被讀入時,會有部分文件暫時隱藏在記憶體中)。
 

轉自https://blog.csdn.net/qiang_xi/article/details/50098533,感謝作者的無私分享。

解析xml檔案常用的幾種方式也就dom,sax,pull了,並且面試官經常問到的也是這三種解析方式之間的優缺點以及使用情況,先說一下這三種方式的優缺點和使用情況吧:

其實dom,sax,pull之間的優缺點網上有很多,講的搞不好比我的還要深入和貼切,不過還是要說一下,畢竟自己以後還是要回顧知識的,到時候不要再翻別人的文章了,

dom解析:優點:簡單易學,可以對文件進行修改,如刪除節點,增加節點等,靈活性較高,讀取速度快,缺點:解析時,把整個xml文件讀到記憶體中,佔用較多資源,

使用範圍:如果需要對文件進行修改,dom解析肯定是首選,如果只是讀取文件中的某些內容或全部內容,且不作修改,建議不要使用dom解析,而用sax解析或者pull解析.

sax解析:優點:解析速度快,不需要把文件讀入記憶體,並且可以根據自身需求來獲取資料,而不必解析整個文件,缺點:需要自己編寫事件邏輯,使用麻煩,並且不能同時訪問文件中的不同部分.使用範圍:對於較大的文件有較好的解析能力,所以適用大文件的解析

pull解析:自我感覺是sax的升級版,比sax好用多了,優點:你不必自己寫事件邏輯,並且也不需要把文件讀到記憶體,缺點:對文件進行修改較困難

ok,三種方式的優缺點及適用範圍說明完畢,其實,任何一種解析方式肯定都可以解析xml,如果你願意,你可以對任何xml文件都使用1種解析方式.下面具體說一下每種解析方式的用法

在解析之前,先把要解析的xml文件貼出來:

<note name="rqq">
    <to id="1">George</to>
    <from id="2">John</from>
    <heading id="3">Reminder</heading>
    <body id="4">Don't forget the meeting!</body>
</note>
1,dom解析:

因為document是一個介面,沒有構造方法,所以不能採用new的方式來獲取例項,只能通過相關類來獲取例項,
檢視dom解析的相關類,可以發現利用DocumentBuilder類可以獲得一個document例項,但是DocumentBuilder類是一個抽象類,也不能採用new的方式獲取DocumentBuilder物件,並且這個類也沒有給出其他方式來獲取例項,所以也只能通過相關類來獲取例項,然後發現DocumentBuilder有個工廠類,即DocumentBuilderFactory,這個類提供了獲取DocumentBuilder例項的方法,但是DocumentBuilderFactory這個類也是個抽象類,不過這個類提供了其他方法來獲取自身的例項,即DocumentBuilderFactory.newInstance(),OK,通過以上方式我們拿到了document物件,程式碼表示出來就是:
ocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(is);

其實不只是dom解析,sax解析和pull解析都是不能直接new出例項的,都只能採用這種方式來獲取解析例項,這應該是出於執行緒安全方面考慮,讓程式自始至終只有一個解析例項.
OK,拿到document物件之後,就可以進行xml文件的解析工作,

因為dom解析的特點:把拿到的xml文件一次全部讀到記憶體裡,所以document肯定有個方法用來獲取整個文件的所有資訊的,即
 Element element = document.getDocumentElement();//獲取當前xml文件的所有資訊
 通過這個方法,我們又拿到一個element物件,這個element物件裡就包含了所有的文件資訊,所以element物件肯定提供了很多方法用來獲取文件詳細資訊,下面用程式碼帶一一展示:

    /**
     * dom解析,該方法裡的程式碼演示瞭如何利用DOM解析,拿到該xml文件的所有資訊,如根節點名稱,屬性,和該根節點包含的
     * 所有子節點的所有資訊(子節點的屬性,文字內容,子節點又包含的子節點等等)
     */
   

 public void DomParse(View view) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            //從assets目錄獲取xml文件輸入流
            AssetManager assets = getAssets();
            InputStream is = assets.open("text.xml");
            //通過 builder.parse(is)方法獲取一個document例項
            Document document = builder.parse(is);
            //根節點相關
            Element element = document.getDocumentElement();//獲取當前xml文件的所有資訊
            element.getTagName();//返回根節點的名稱,這裡為:note
            element.getAttribute("name");//根據根節點的屬性名返回屬性值,這裡為:rqq
            //子節點相關
            NodeList nodeList = element.getElementsByTagName("*");//根據子節點的節點名返回一個節點列表,如果填寫"*",則返回所有節點的列表資訊
            nodeList.item(0).getNodeName();//返回第0個位置子節點的節點名,這裡為:to
            nodeList.item(0).getTextContent();//返回第0個位置子節點裡的文字內容,不是屬性值,這裡為:George
            nodeList.item(0).getParentNode();//返回第0個位置子節點的父節點,因各種原因找不到父節點則返回null,這裡為:note
            nodeList.item(0).getChildNodes();//返回第0個位置子節點包含的所有子節點列表資訊,沒有子節點,也會返回一個NodeList,只不過長度為0,這裡當前的子節點<span style="white-space:pre">                    </span>     //下面已經沒有子節點了,所以返回的NodeList長度為0
            NamedNodeMap map = nodeList.item(0).getAttributes();//獲取第0個位置子節點所有的屬性集合,這裡只有1個屬性,即id
            String nodeValue = map.item(0).getNodeValue();//獲取第0個位置子節點的第0個位置屬性的屬性值,這裡為:1
            /*
            * //也可通過下面的方式獲取
            * //因為Element是Node的子類,所以可以把Node強轉為Element
            * Element element1 = (Element) nodeList.item(0);
            * //然後通過Element拿到該子節點的各種屬性值
            * element1.getAttribute("id");//獲取該子節點的id屬性
            * //因為Element繼承自Node,所以Element可以使用父類即Node類的所有的public方法,如下:
            * element1.getNodeName();
            * element1.getTextContent();
            * element1.getParentNode();
            * element1.getChildNodes();
            *
            * */
 
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }


程式碼裡已經註釋的相當詳細,這裡就不多說了
2,sax解析:

sax解析主要需要繼承DefaultHandler,然後重寫一些方法,一般需要重寫的方法為:

startDocument(),
endDocument(),
startElement(),
endElement()
haracters()
error().
在這些方法中定義自己想要實現的功能即可,具體程式碼如下,
    /**
     * sax解析
     */

    public void SaxParse(View view) {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            SAXParser saxParser = factory.newSAXParser();
            AssetManager assets = getAssets();
            InputStream is = assets.open("text.xml");
            saxParser.parse(is, new SaxParseUtils());
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

SaxParseUtils類:

package com.rqq.xmlparse.utils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
 
public class SaxParseUtils extends DefaultHandler {
    public SaxParseUtils() {
    }
 
    /**
     * 文件開始解析時呼叫
     *
     * @throws SAXException
     */
    @Override
    public void startDocument() throws SAXException {
        System.out.println("文件開始解析...");
    }
 
    /**
     * 相當於一個迴圈,從根節點開始,每讀到一個新的節點,
     * 便執行一次這個方法,然後把該節點裡的所有屬性都存到Attributes裡,
     *
     * @param uri        xml文件的uri地址,這裡直接傳入的是inputStream,所以uri為空
     * @param localName  本地名,一般和qName相同
     * @param qName      節點名
     * @param attributes 當前節點下的所有屬性都存放到該引數裡了
     * @throws SAXException 包含任何sax解析的異常
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        System.out.println(qName + "節點開始讀取");
        attributes.getLength();//獲取屬性數量
        String value = attributes.getValue(0);//也可根據索引號獲取屬性值,這裡只有id一個屬性,所以索引為0
        String QName = attributes.getQName(0);
        System.out.println("節點名: " + qName + ",屬性名: " + QName + ",屬性值: " + value + ";");
        //System.out.println("uri: " + uri + "," + "localName: " + localName + "," + "qName: " + qName + "," + "attributes: " + attributes + ";");
    }
 
    /**
     * 文件解析完畢時呼叫
     *
     * @throws SAXException
     */
    @Override
    public void endDocument() throws SAXException {
        System.out.println("文件解析結束...");
    }
 
    /**
     * 該方法裡存放的是每個節點裡的文字內容,文字內容存到ch數組裡,
     *
     * @param ch     用來存放每個節點裡的文字內容
     * @param start  文字內容是從哪開始的,如果文字內容前面有1個空格,則start為1,如果沒有空格,則start為0
     * @param length ch陣列的長度,包含空格
     * @throws SAXException 各種sax解析異常
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        String data = new String(ch, start, length);
        if (!data.trim().equals(""))
            System.out.println("該節點存放的文字內容: " + data);
        //System.out.println("ch: " + ch + "," + "start: " + start + "," + "length: " + length + ";");
 
    }
 
    /**
     * 每個節點讀取完畢時呼叫
     *
     * @param uri       xml文件的uri地址,也可以為url,這裡直接傳入的是inputStream,所以uri為空
     * @param localName 本地名,一般和qName相同
     * @param qName     節點名
     * @throws SAXException 各種sax解析異常
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        //System.out.println("uri: " + uri + "," + "localName: " + localName + "," + "qName: " + qName + ";");
        System.out.println(qName + "節點讀取完畢-----------");
 
    }
 
    /**
     * 文件解析發生錯誤時呼叫
     *
     * @param e 具體的異常資訊
     * @throws SAXException 各種sax解析異常
     */
    @Override
    public void error(SAXParseException e) throws SAXException {
        System.out.println("文件解析錯誤: " + e);
 
    }
}


程式碼註釋的也很詳細,直接貼出結果吧:

重寫的方法裡的引數說明

獲取到的文件的具體內容為:

''
3,pull解析,

pull解析主要有兩個關鍵點,1,獲取到eventType,2,利用while迴圈,如下程式碼:

 /**
     * pull解析
     */
   

 public void PullParse(View view) {
        try {
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser pull = factory.newPullParser();
            AssetManager assets = getAssets();
            InputStream is = assets.open("text.xml");
            pull.setInput(is, "utf-8");
            int eventType = pull.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT:
                        System.out.println("pull開始解析文件...");
                        break;
                    case XmlPullParser.START_TAG:
                        System.out.println("讀取到節點: " + pull.getName() +
                                ",屬性名:" + pull.getAttributeName(0) +
                                ",屬性值:" + pull.getAttributeValue(0));
                        break;
                    case XmlPullParser.TEXT:
                        if (!pull.getText().trim().equals(""))
                            System.out.println("讀取到的文字內容:" + pull.getText());
                        break;
                    case XmlPullParser.END_TAG:
                        System.out.println(pull.getName() + "節點讀取完畢-------");
                        break;
                }
                eventType = pull.next();//這是關鍵,不然迴圈沒有意義
            }
            System.out.println("pull解析文件完畢...");
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}