xml解析之stax

博文引自:http://zangweiren.iteye.com/blog/647334java

Java 6.0對XML支持的新特性有許多方面。好比StAX、針對XML-Web服務的Java架構(JAX-WS)2.0、針對XML綁定的API(JAXB)2.0、XML數字簽名API,甚至還支持SQL:2003 'XML'數據類型。在這一篇文章中咱們將要介紹的是StAX技術,由於它在咱們的開發中將被使用地更加頻繁。 

StAX是Streaming API for XML的縮寫,是一種針對XML的流式拉分析API。關於對XML進行分析(或解析)的技術,你們必定都不陌生了。在Java 6.0以前,就已經有四種: 網絡

  1. DOM:Document Object Model
  2. SAX:Simple API for XML
  3. JDOM:Java-based Document Object Model
  4. DOM4J:Document Object Model for Java


關於它們的解析原理,以及性能和優缺點,我會在本文的結尾作一個簡要的介紹。這篇文章中,咱們主要說說StAX這種新的解析方式。 

首先咱們來搞清楚兩個概念:推分析拉分析。 

在程序中訪問和操做XML文件通常有兩種模型:DOM(文檔對象模型)和流模型。它們的優缺點以下: 

架構

引用
DOM優勢:容許編輯和更新XML文檔,能夠隨機訪問文檔中的數據,可使用XPath(XML Path Language,是一種從XML文檔中搜索節點的查詢語言)查詢。 
DOM缺點:須要一次性加載整個文檔到內存中,對於大型文檔,會形成性能問題。



引用
流模型優勢:對XML文件的訪問採用流的概念,在任什麼時候候內存中只有當前節點,解決了DOM的性能問題。 
流模型缺點:是隻讀的,而且只能向前,不能在文檔中執行向後導航操做。



關於什麼是DOM,文章結尾處會有介紹。這裏咱們簡單說一下流:它是一個連續的字節序列,能夠理解爲不停地從源頭向目標搬運着字節的特殊對象。 

讓咱們回到主題。流模型每次迭代XML文檔中的一個節點,適合於處理較大的文檔,所耗內存空間小。它有兩種變體--「推」模型和「拉」模型。 

框架

引用
推模型:就是咱們常說的SAX,它是一種靠事件驅動的模型。當它每發現一個節點就引起一個事件,而咱們須要編寫這些事件的處理程序。這樣的作法很麻煩,且不靈活。



引用
拉模型:在遍歷文檔時,會把感興趣的部分從讀取器中拉出,不須要引起事件,容許咱們選擇性地處理節點。這大大提升了靈活性,以及總體效率。



到此,咱們就弄明白了「推分析」和「拉分析」的概念: 

ide

引用
基於流模型中推模型的分析方式稱爲推分析;基於流模型中拉模型的分析方式就稱爲拉分析。



StAX就是一種拉分析式的XML解析技術。它也支持對XML文件的生成操做,可是這篇文章裏咱們只介紹有關解析的知識。 

從一開始,JAXP(Java API for XML Processing)就提供了兩種方法來處理XML:DOM和SAX。StAX是一種面向流的新方法,最終版本於2004年3月發佈,併成爲JAXP 1.4(包含在Java 6.0中)的一部分。StAX的實現使用了JWSDP(Java Web Services Development Pack)1.6,並結合了SJSXP(Sun Java System XML Streaming Parser,位於javax.xml.stream.*包中)。 

JWSDP是用來開發Web Services、Web應用程序以及Java應用(主要是XML處理)的開發包。它包含的Java API有: 模塊化

  • JAXP:Java API for XML Processing
  • JAXB:Java Architecture for XML Binding
  • JAX-RPC:Java API for XML-based Remote Procedure Calls
  • JAX-WS:Java API for XML Web Services
  • SAAJ:SOAP with Attachments API for Java
  • JAXR:Java API for XML Registries
  • Web Services Registry



JWSDP的早期版本中還包括: 性能

  • Java Servlet
  • JSP:JavaServer Pages
  • JSF:JavaServer Faces



如今,JWSDP已經被GlassFish所替代。 

StAX包括兩套處理XML的API,分別提供了不一樣程度的抽象。它們是:基於指針的API和基於迭代器的API。 

咱們先來了解基於指針的API。它把XML做爲一個標記(或事件)流來處理,應用程序能夠檢查解析器的狀態,得到解析的上一個標記的信息,而後再處理下一個標記,依次類推。 

在開始API探索以前,咱們首先建立一個名爲users.xml的XML文檔用於測試,它的內容以下: 

測試

Xml代碼   收藏代碼
<?xml version="1.0" encoding="UTF-8"?>  
<company>  
    <depart title="Develop Group">  
        <user name="Tom" age="28" gender="male" >Manager</user>  
        <user name="Lily" age="26" gender="female" />  
    </depart>  
    <depart title="Test Group">  
        <user name="Frank" age="32" gender="male" >Team Leader</user>  
        <user name="Bob" age="45" gender="male" />  
        <user name="Kate" age="25" gender="female" />  
    </depart>  
</company>  

 


可讓咱們使用基於指針的API的接口是javax.xml.stream.XMLStreamReader(很遺憾,你不能直接實例化它),要獲得它的實例,咱們須要藉助於javax.xml.stream.XMLInputFactory類。根據JAXP的傳統風格,這裏使用了抽象工廠(Abstract Factory)模式。若是你對這個模式很熟悉的話,就可以在腦海中想象出咱們將要編寫的代碼的大體框架了。 

首先,得到一個XMLInputFactory的實例。方法是: 

this

Java代碼   收藏代碼
  1. XMLInputFactory factory = XMLInputFactory.newInstance();  


或者: 

編碼

Java代碼   收藏代碼
  1. XMLInputFactory factory = XMLInputFactory.newFactory();  



這兩個方法是等價的,它們都是建立了一個新的實例,甚至實例的類型都是徹底一致的。由於它們的內部實現都是: 

Java代碼   收藏代碼
{  
    return (XMLInputFactory) FactoryFinder.find("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");  
}  

 


接下來咱們就能夠建立XMLStreamReader實例了。咱們有這樣一組方法能夠選擇: 

Java代碼   收藏代碼
XMLStreamReader createXMLStreamReader(java.io.Reader reader) throws XMLStreamException;  
  
XMLStreamReader createXMLStreamReader(javax.xml.tranform.Source source) throws XMLStreamException;  
      
XMLStreamReader createXMLStreamReader(java.io.InputStream stream) throws XMLStreamException;  
  
XMLStreamReader createXMLStreamReader(java.io.InputStream stream, String encoding) throws XMLStreamException;  
  
XMLStreamReader createXMLStreamReader(String systemId, java.io.InputStream stream) throws XMLStreamException;  
  
XMLStreamReader createXMLStreamReader(String systemId, java.io.Reader reader) throws XMLStreamException;  

 


這些方法都會根據給定的流建立一個XMLStreamReader實例,你們能夠依據流的類型、是否須要指定解析XML的編碼或者systemId來選擇相應的方法。 

在這裏,咱們對systemId稍做說明,並簡單解釋一下它與publicId的區別。 

systemId和publicId是XML文檔裏DOCTYPE元素中常常出現的兩個屬性。它們都是對外部資源的引用,用以指明引用資源的地址。systemId是直接引用資源,publicId是間接定位外部資源。具體一點說是這樣: 

引用
systemId:外部資源(大可能是DTD文件)的URI。好比本地文件file:///user/dtd/users.dtd或者網絡某個地址的文件http://www.w3.org/dtd/users.dtd。



引用
publicId:至關於一個名字,這個名字表明瞭一個外部資源。好比,咱們規定"W3C HTML 4.0.1"這個字符串對應"http://www.w3.org/dtd/users.dtd"這個資源。那麼,publicId="W3C HTML 4.0.1"和systemId="http://www.w3.org/dtd/users.dtd"的做用就是同樣的。



好了,咱們接着用以上列出的第一個接口來建立一個XMLStreamReader實例: 

Java代碼   收藏代碼
try {  
    XMLStreamReader reader = factory.createXMLStreamReader(new FileReader("users.xml"));  
} catch (FileNotFoundException e) {  
    e.printStackTrace();  
} catch (XMLStreamException e) {  
    e.printStackTrace();  
}  

 


要遍歷XML文檔,須要用到XMLStreamReader的下面幾個方法: 

Java代碼   收藏代碼
int getEventType();  
  
boolean hasNext() throws XMLStreamException;  
  
int next() throws XMLStreamException;  



getEventType()方法返回XMLStreamConstants接口中定義的一個標記常量,表示當前指針所指向標記(或事件)的類型。根據當前事件類型的不一樣,應用程序能夠作出不一樣的處理。標記常量的類型和含義以下: 

START_DOCUMENT:文檔的開始
END_DOCUMENT:文檔的結尾
START_ELEMENT:元素的開始
END_ELEMENT:元素的結尾
PROCESSING_INSTRUCTION:處理指令
CHARACTERS:字符(文本或空格)
COMMENT:註釋
SPACE:可忽略的空格
ENTITY_REFERENCE:實體的引用
ATTRIBUTE:元素的屬性
DTD:DTD
CDATA:CDATA塊
NAMESPACE:命名空間的聲明
NOTATION_DECLARATION:標記的聲明
ENTITY_DECLARATION:實體的聲明

 



next()方法將指針移動到下一個標記,它同時返回這個標記(或事件)的類型。此時若接着調用getEventType()方法則返回相同的值。 

hasNext()用於判斷是否還有下一個標記。只有當它返回true時才能夠調用next()以及其它移動指針的方法。 

看了上面幾個方法的介紹,你們就會發現使用XMLStreamReader遍歷XML文檔是很是容易的,由於它的用法和每一個人都熟悉的Java迭代器(Iterator)是同樣的。下面咱們就用已經掌握的這幾個方法對上文中給出的XML文檔作一個測試。但願你還記得它的內容,若是忘記了,請翻回去從新瀏覽一下。 

咱們的測試代碼以下: 

Java代碼   收藏代碼
/** 
 * 列出全部用戶 
 *  
 * @author zangweiren 2010-4-17 
 *  
 */  
public class ListUsers {  
    // 得到解析器  
    public static XMLStreamReader getStreamReader() {  
        String xmlFile = ListUsers.class.getResource("/").getFile()  
                + "users.xml";  
        XMLInputFactory factory = XMLInputFactory.newFactory();  
        try {  
            XMLStreamReader reader = factory  
                    .createXMLStreamReader(new FileReader(xmlFile));  
            return reader;  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    // 列出全部用戶名稱  
    public static void listNames() {  
        XMLStreamReader reader = ListUsers.getStreamReader();  
        // 遍歷XML文檔  
        try {  
            while (reader.hasNext()) {  
                int event = reader.next();  
                // 若是是元素的開始  
                if (event == XMLStreamConstants.START_ELEMENT) {  
                    // 列出全部用戶名稱  
                    if ("user".equalsIgnoreCase(reader.getLocalName())) {  
                        System.out.println("Name:"  
                                + reader.getAttributeValue(null, "name"));  
                    }  
                }  
            }  
            reader.close();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
    }  
  
    public static void main(String[] args) {  
        ListUsers.listNames();  
    }  
}  
View Code

 



運行結果: 

引用
Name:Tom 
Name:Lily 
Name:Frank 
Name:Bob 
Name:Kate



在上面的示例代碼中,咱們用到了XMLStreamReader的兩個新方法: 

Java代碼   收藏代碼
String getLocalName();  
  
String getAttributeValue(String namespaceURI, String localName);  

 


與此相關的還有一個方法: 

Java代碼   收藏代碼
QName getName();  

 


這三個方法牽扯到XML的namespace(命名空間)、localName(本地名稱)、QName(Qualified Name,限定名稱)三個概念,咱們順便解釋一下: 

命名空間是爲了支持相同名稱不一樣含義的XML標籤而產生的,它能夠這麼定義: 

Xml代碼   收藏代碼
<com:company xmlns:com="http://www.zangweiren.com/company">  
    <!-- here is other tags -->  
</com:company>  

 


其中,com是命名空間的前綴,company是命名空間的標籤,http://www.zangweiren.com/company是命名空間的標識,相同的標識被認爲是同一個命名空間。標識又叫URI,是惟一的,有URL(統一資源定位器)和URN(統一資源名稱)兩種。前綴是命名空間的簡寫,目的是爲了使用方便。命名空間被聲明後就能夠被使用: 

Xml代碼   收藏代碼
<com:company xmlns:com="http://www.zangweiren.com/company">  
    <com:depart name="Develop Group" />  
</com:company>

  



在上例的<com:depart />標籤中,前綴com是命名空間,depart是localName,這兩個合起來就是QName。 

在明白了這三個XML基本概念以後,也就明白了getLocalName()和getAttributeValue(String namespaceURI, String localName)方法的含義。 

如今,咱們已經學會了使用XMLStreamReader遍歷XML文檔,並對特定標籤進行解析了。 

咱們再來看看下面兩個方法: 

Java代碼   收藏代碼
String getElementText() throws XMLStreamException;  
  
int nextTag() throws XMLStreamException;

 


getElementText()方法返回元素的開始標籤(START_ELEMENT)和關閉標籤(END_ELEMENT)之間的全部文本內容,若遇到嵌套的元素就會拋出異常。 

nextTag()方法將跳過全部空白、註釋或處理指令,直到遇到START_ELEMENT或END_ELEMENT。它在解析只含元素內容的XML文檔時頗有用。不然,在發現標記以前遇到非空白文本(不包括註釋和處理指令),就會拋出異常。 

好比咱們修改上一個測試程序,增長一個新方法: 

Java代碼   收藏代碼
// 列出全部用戶的名稱和年齡  
public static void listNamesAndAges() {  
    XMLStreamReader reader = ListUsers.getStreamReader();  
    try {  
        while (reader.hasNext()) {  
            // 跳過全部空白、註釋或處理指令,到下一個START_ELEMENT  
            int event = reader.nextTag();  
            if (event == XMLStreamConstants.START_ELEMENT) {  
                if ("user".equalsIgnoreCase(reader.getLocalName())) {  
                    System.out.println("Name:"  
                            + reader.getAttributeValue(null, "name")  
                            + ";Age:"  
                            + reader.getAttributeValue(null, "age"));  
                }  
            }  
        }  
        reader.close();  
    } catch (XMLStreamException e) {  
        e.printStackTrace();  
    }  
}  

 



而後把它添加到主方法中: 

Java代碼   收藏代碼
public static void main(String[] args) {  
        ListUsers.listNames();  
        ListUsers.listNamesAndAges();  
    }  

 



運行它試試看,在解析到<user name="Tom" age="28" gender="male" >Manager</user>的時候會報錯,所以你會獲得一個相似這樣的錯誤信息: 

javax.xml.stream.XMLStreamException: ParseError at [row,col]:[4,53] 
Message: found: CHARACTERS, expected START_ELEMENT or END_ELEMENT 

對於基於指針的XMLStreamReader來講,雖然API文檔說的是「事件」,可是咱們把它當作「標記」更易於理解,並且不會與另外一套基於事件的API相混淆。 

XMLStreamReader的某些方法,不管當前標記(或事件)是什麼類型的,均可以被調用。它們的定義和做用以下: 

  • String getVersion();//得到XML文檔中的版本信息
  • String getEncoding();//得到XML文檔中的指定編碼
  • javax.xml.namespace.NamespaceContext getNamespaceContext();//得到當前有效的命名空間上下文,包含前綴、URI等信息
  • String getNamespaceURI();//得到當前有效的命名空間的URI
  • javax.xml.stream.Location getLocation();//得到當前標記的位置信息,包含行號、列號等
  • boolean hasName();//判斷當前標記是否有名稱,好比元素或屬性
  • boolean hasText();//判斷當前標記是否有文本,好比註釋、字符或CDATA
  • boolean isStartElement();//判斷當前標記是不是標籤開始
  • boolean isEndElement();//判斷當前標記是不是標籤結尾
  • boolean isCharacters();//判斷當前標記是不是字符
  • boolean isWhiteSpace();//判斷當前標記是不是空白



對於以上方法都很容易理解和記憶,咱們再也不編寫代碼展現它們的效果。 

讓咱們看看有關屬性操做方法。仍是首先熟悉一下它們的定義: 

Java代碼   收藏代碼
int getAttributeCount();  
  
String getAttributeLocalName(int index);  
  
QName getAttributeName(int index);  
  
String getAttributeNamespace(int index);  
  
String getAttributePrefix(int index);  
  
String getAttributeType(int index);  
  
String getAttributeValue(int index);  
  
String getAttributeValue(String namespaceURI, String localName);

 

  


這些方法都十分容易理解,基本上看方法的名稱和參數就知道它的用途了。並且最後一個方法在上面的示例中咱們已經用過了。讓咱們再用一個簡單的示例程序進一步加深對這些方法的認識。 

Java代碼   收藏代碼
// 列出全部用戶的名稱和年齡  
public static void listNamesAndAges() {  
    XMLStreamReader reader = ListUsers.getStreamReader();  
    try {  
        while (reader.hasNext()) {  
            // 跳過全部空白、註釋或處理指令,到下一個START_ELEMENT  
            int event = reader.nextTag();  
            if (event == XMLStreamConstants.START_ELEMENT) {  
                if ("user".equalsIgnoreCase(reader.getLocalName())) {  
                    System.out.println("Name:"  
                            + reader.getAttributeValue(null, "name")  
                            + ";Age:"  
                            + reader.getAttributeValue(null, "age"));  
                }  
            }  
        }  
        reader.close();  
    } catch (XMLStreamException e) {  
        e.printStackTrace();  
    }  
}  

 



把它加入到主方法中: 

Java代碼   收藏代碼
public static void main(String[] args) {  
        ListUsers.listNames();  
        // ListUsers.listNamesAndAges();  
        ListUsers.listAllAttrs();  
}  

 



運行結果: 

引用
1.name=Tom;age=28;gender=male; 
2.name=Lily;age=26;gender=female; 
3.name=Frank;age=32;gender=male; 
4.name=Bob;age=45;gender=male; 
5.name=Kate;age=25;gender=female;



相信你看到這裏,已經能夠順利地使用XMLStreamReader來完成XML文檔的解析了。 

上面咱們介紹了基於指針的StAX API。這種方式儘管效率高,可是沒有提供XML結構的抽象,所以是一種低層API。 

較爲高級的基於迭代器的API容許應用程序把XML做爲一系列事件對象來處理,每一個對象和應用程序交換XML結構的一部分。應用程序只須要肯定解析事件的類型,將其轉換成對應的具體類型,而後利用其方法得到屬於該事件對象的信息。 

StAX中基於迭代器的API是一種面向對象的方式,這也是它與基於指針的API的最大區別。它經過將事件轉變爲對象,讓應用程序能夠用面向對象的方式處理它們,這有利於模塊化和不一樣組件之間的代碼重用。 

事件迭代器API的主要接口是javax.xml.stream.XMLEventReader和javax.xml.stream.events.XMLEvent。XMLEventReader和XMLStreamReader相比要簡單的多,這是由於關於解析事件的全部信息都封裝在了事件對象(XMLEvent)中。 

建立XMLEvent對象前一樣須要一個XMLInputFactory實例。它有以下這些建立XMLEvent實例的方法: 

Java代碼   收藏代碼
XMLEventReader createXMLEventReader(java.io.InputStream stream) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(java.io.InputStream stream, String encoding) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(java.io.Reader reader) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(String systemId, java.io.InputStream stream) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(String systemId, java.io.Reader reader) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(Source source) throws XMLStreamException;  
      
XMLEventReader createXMLEventReader(XMLStreamReader reader) throws XMLStreamException;  

 



最後一個方法不一樣與其它的,它是將一個XMLStreamReader對象轉換成一個XMLEventReader對象。值得注意的是,XMLInputFactory沒有提供將XMLEventreader對象轉換成XMLStreamreader對象的方法。我想,在咱們的開發過程當中,應該不會出現這種須要將高層API轉換成低層API來使用的狀況。 

XMLEventReader接口擴展了java.util.Iterator接口,它定義瞭如下幾個方法: 

Java代碼   收藏代碼
String getElementText() throws XMLStreamException;  
  
boolean hasNext();  
  
XMLEvent nextEvent() throws XMLStreamException;  
  
XMLEvent nextTag() throws XMLStreamException;  
  
XMLEvent peek() throws XMLStreamException;  

 



其中,getElementText()、hasNext()、nextTag()三個方法的含義及用法相似於XMLStreamReader,而nextEvent()方法相似於XMLStreamReader的next()方法。因此,這裏只對peed()方法作一下說明。 

調用peek()方法,你將獲得下一個事件對象。它與nextEvent()方法的不一樣是,當你連續兩次或兩次以上調用它時,你獲得的都是同一個事件對象。 

咱們再看看XMLEvent接口中定義的方法。這些方法大致能夠分爲三種類別。第一類是用於事件類型判斷的: 

  • boolean isAttribute();//判斷該事件對象是不是元素的屬性
  • boolean isCharacters();//判斷該事件對象是不是字符
  • boolean isStartDocument();//判斷該事件對象是不是文檔開始
  • boolean isEndDocument();//判斷該事件對象是不是文檔結尾
  • boolean isStartElement();//判斷該事件對象是不是元素開始
  • boolean isEndElement();//判斷該事件對象是不是元素結尾
  • boolean isEntityReference();//判斷該事件對象是不是實體的引用
  • boolean isNamespace();//判斷該事件對象是不是命名空間
  • boolean isProcessingInstruction();//判斷該事件對象是不是處理指令



第二類是將XMLEvent轉換爲具體的子類對象的: 

  • Characters asCharacters();//轉換爲字符事件對象
  • StartElement asStartElement();//轉換爲標籤開始事件對象
  • EndElement asEndElement();//轉換爲標籤結尾事件對象



第三類是獲取事件對象通用信息的: 

javax.xml.stream.Location getLocation();//得到事件對象的位置信息,相似於XMLStreamReader的getLocation()方法
int getEventType();//得到事件對象的類型,相似於XMLStreamReader的getEventType()方法

 


其中,getEventType()方法的返回值也是XMLStreamConstants中定義的常量,其類型和含義與XMLStreamReader的getEventType()方法的返回值徹底相同。 

下面讓咱們用一段示例代碼來熟悉基於迭代器的StAX API的使用方法,進而引出XMLEvent接口的子接口類型。咱們仍然使用users.xml做爲測試文件: 

Java代碼   收藏代碼
// 列出全部信息  
    @SuppressWarnings("unchecked")  
    public static void listAllByXMLEventReader() {  
        String xmlFile = ListUsers.class.getResource("/").getFile()  
                + "users.xml";  
        XMLInputFactory factory = XMLInputFactory.newInstance();  
        try {  
            // 建立基於迭代器的事件讀取器對象  
            XMLEventReader reader = factory  
                    .createXMLEventReader(new FileReader(xmlFile));  
            // 遍歷XML文檔  
            while (reader.hasNext()) {  
                XMLEvent event = reader.nextEvent();  
                // 若是事件對象是元素的開始  
                if (event.isStartElement()) {  
                    // 轉換成開始元素事件對象  
                    StartElement start = event.asStartElement();  
                    // 打印元素標籤的本地名稱  
                    System.out.print(start.getName().getLocalPart());  
                    // 取得全部屬性  
                    Iterator attrs = start.getAttributes();  
                    while (attrs.hasNext()) {  
                        // 打印全部屬性信息  
                        Attribute attr = (Attribute) attrs.next();  
                        System.out.print(":" + attr.getName().getLocalPart()  
                                + "=" + attr.getValue());  
                    }  
                    System.out.println();  
                }  
            }  
            reader.close();  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
    }  

 



把它加到主程序中: 

Java代碼   收藏代碼
public static void main(String[] args) {  
        ListUsers.listNames();  
        // ListUsers.listNamesAndAges();  
        ListUsers.listAllAttrs();  
        ListUsers.listAllByXMLEventReader();  
    }  

 



運行後獲得以下結果: 

引用
company 
depart:title=Develop Group 
user:age=28:name=Tom:gender=male 
user:age=26:name=Lily:gender=female 
depart:title=Test Group 
user:age=32:name=Frank:gender=male 
user:age=45:name=Bob:gender=male 
user:age=25:name=Kate:gender=female



這個例子中,咱們利用基於迭代器的StAX API打印出了全部元素的本地名稱以及它們的所有屬性信息。你們能夠看到,它的用法與基於指針的StAX API的用法十分類似。可是因爲使用了面向對象的思想,更加容易理解。 

咱們用到了兩個新的接口:StartElement和Attribute。它們都是XMLEvent接口的子接口,且都在javax.xml.stream.events.*包中。它們是更具體的事件對象類型。實際上在javax.xml.stream.events中,除了XMLEvent接口自身外,其他接口都是它的子接口。它們的名稱和表明的具體事件對象類型以下: 

Attribute:元素的屬性
Characters:字符
Comment:註釋
DTD:DTD
StartDocument:文檔的開始
EndDocument:文檔的結束
StartElement:元素的開始
EndElement:元素的結束
EntityDeclaration:實體聲明
EntityReference:實體的引用
Namespace:命名空間聲明
NotationDeclaration:標記的聲明
ProcessingInstruction:處理指令

 



你可能以爲這些類看着很眼熟,由於它們在XMLStreamReader的getEventType()方法的返回值,也就是XMLStreamConstants中定義的常量中,都能找到一一的對應。惟獨缺乏了SAPCE(可忽略的空白)和CDATA(CDATA塊)。也就是說,在基於指針的StAX API中定義事件類型,在基於迭代器的StAX API中都是以對象的形式提供給應用程序的,這就是爲何說後者是一種更具備面向對象思想的高層API的緣由。 

這些事件對象接口不只表明了一種事件類型,還包含對應事件對象的信息。至於它們所具備的方法大可能是獲取事件對象信息的訪問器,其含義及具體用法,都很容易理解和使用,所以再也不詳細介紹。 

你們可能注意到,XMLEvent只提供了三個asXXX()形式的方法將它轉換到具體的子類型,若是你想要處理的事件對象類型在這三種類型以外,直接使用強制類型轉換就能夠了。 

如今咱們掌握了StAX的基於指針的拉分析API和基於迭代器的拉分析API的基本應用。咱們再來看一種稍微高級的用法,它能夠幫助咱們更好地完成XML文檔的解析工做。 

XMLInputFactory還有兩個建立流讀取器的方法: 

Java代碼   收藏代碼
XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) throws XMLStreamException;  
      
XMLEventReader createFilteredReader(XMLEventReader reader, EventFilter filter) throws XMLStreamException;  

 



它們分別爲XMLStreamReader和XMLEventReader增長一個過濾器,過濾掉不須要解析的內容,只留下應用程序關心的信息用於解析。雖然咱們能夠在應用程序中作一樣的過濾工做,就像以前示例程序中所寫的那樣,可是把過濾工做交給過濾器的好處是,讓應用程序能夠更加專一於解析工做,而且對於通用的過濾(好比註釋),將它放到過濾器中能夠實現過濾邏輯部分代碼的重用。這符合軟件設計原則。 

若是你編寫過文件過濾器java.io.FileFilter的話,那麼編寫StreamFilter和EventFilter就更加容易。咱們先來看看這兩個接口的定義: 

Java代碼   收藏代碼
public interface StreamFilter {  
  public boolean accept(XMLStreamReader reader);  
}  
  
public interface EventFilter {  
  public boolean accept(XMLEvent event);  
}  

 



咱們就以StreamFilter爲例來演示過濾器的用法。爲此,咱們使用users.xml爲測試文檔編寫一段新的程序: 

Java代碼   收藏代碼
/** 
 * StreamFilter示例程序 
 *  
 * @author zangweiren 2010-4-19 
 *  
 */  
public class TestStreamFilter implements StreamFilter {  
  
    public static void main(String[] args) {  
        TestStreamFilter t = new TestStreamFilter();  
        t.listUsers();  
    }  
  
    @Override  
    public boolean accept(XMLStreamReader reader) {  
        try {  
            while (reader.hasNext()) {  
                int event = reader.next();  
                // 只接受元素的開始  
                if (event == XMLStreamConstants.START_ELEMENT) {  
                    // 只保留user元素  
                    if ("user".equalsIgnoreCase(reader.getLocalName())) {  
                        return true;  
                    }  
                }  
                if (event == XMLStreamConstants.END_DOCUMENT) {  
                    return true;  
                }  
            }  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
        return false;  
    }  
  
    public XMLStreamReader getFilteredReader() {  
        String xmlFile = TestStreamFilter.class.getResource("/").getFile()  
                + "users.xml";  
        XMLInputFactory factory = XMLInputFactory.newFactory();  
        XMLStreamReader reader;  
        try {  
            reader = factory.createXMLStreamReader(new FileReader(xmlFile));  
            // 建立帶有過濾器的讀取器實例  
            XMLStreamReader freader = factory  
                    .createFilteredReader(reader, this);  
            return freader;  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    public void listUsers() {  
        XMLStreamReader reader = getFilteredReader();  
        try {  
            // 列出全部用戶的名稱  
            while (reader.hasNext()) {  
                // 過濾工做已交由過濾器完成,這裏不須要再作  
                System.out.println("Name="  
                        + reader.getAttributeValue(null, "name"));  
  
                if (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {  
                    reader.next();  
                }  
            }  
            reader.close();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
    }  
  
}  
View Code

 



測試結果: 

引用
Name=Tom 
Name=Lily 
Name=Frank 
Name=Bob 
Name=Kate



你們可能已經發現,這裏有一個與以前處理不一樣的地方,就是咱們先打印了用戶的信息,再調用next()方法;這與java.util.Iterator的先調用next()方法,再獲取對象信息不一樣。而以前咱們一直採用的是與Iterator同樣的處理代碼。這裏,就有一個問題須要說明。 

對於XMLStreamReader的next()方法來講,第一次被調用的時候返回的是第二個標記(或事件)。要得到第一個標記,就須要在調用next()方法以前調用getEventType()方法。這是須要注意的地方。咱們以上的代碼之因此採用Java迭代器同樣的處理方式,是由於第一個標記老是START_DOCUMENT,而咱們不須要對它進行操做,所以就採用了一種熟悉的編碼方式,方便你們理解。XMLEventReader的nextEvent()方法就不存在這樣的問題。 

EventFilter的用法與StreamFilter相同,再也不舉例說明。 

StAX還爲咱們提供了另一種隔離標記或事件對象過濾邏輯的方法,那就是StreamReaderDelegate和EventReaderDelegate這兩個類,它們都位於javax.xml.stream.util.*包中。StAX API中大部分都是接口,這兩個是確確實實的類。它們都作了一樣的工做,就是分別包裝了XMLStreamReader和XMLEventReader,並把全部的方法都委託(Delegate)給它們處理,既沒有增長任何的方法或邏輯,也沒有改變或刪除任何方法,所以這裏使用的是策略(Strategy)模式。咱們能夠採用裝飾(Decorator)模式,給StreamReaderDelegate或EventReaderDelegate增長新的功能。請看下面的例子: 

Java代碼   收藏代碼
/** 
 * 測試StreamReaderDelegate 
 *  
 * @author zangweiren 2010-4-19 
 *  
 */  
public class TestStreamDelegate {  
  
    public static void main(String[] args) {  
        TestStreamDelegate t = new TestStreamDelegate();  
        t.listUsers();  
    }  
  
    public XMLStreamReader getDelegateReader() {  
        String xmlFile = TestStreamFilter.class.getResource("/").getFile()  
                + "users.xml";  
        XMLInputFactory factory = XMLInputFactory.newFactory();  
        XMLStreamReader reader;  
        try {  
            reader = new StreamReaderDelegate(factory  
                    .createXMLStreamReader(new FileReader(xmlFile))) {  
                // 重寫(Override)next()方法,增長過濾邏輯  
                @Override  
                public int next() throws XMLStreamException {  
                    while (true) {  
                        int event = super.next();  
                        // 保留用戶元素的開始  
                        if (event == XMLStreamConstants.START_ELEMENT  
                                && "user".equalsIgnoreCase(getLocalName())) {  
                            return event;  
                        } else if (event == XMLStreamConstants.END_DOCUMENT) {  
                            return event;  
                        } else {  
                            continue;  
                        }  
                    }  
                }  
            };  
            return reader;  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    public void listUsers() {  
        XMLStreamReader reader = this.getDelegateReader();  
        try {  
            while (reader.hasNext()) {  
                reader.next();  
                if (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {  
                    // 列出用戶的名稱和年齡  
                    System.out.println("Name="  
                            + reader.getAttributeValue(null, "name") + ";age="  
                            + reader.getAttributeValue(null, "age"));  
                }  
            }  
            reader.close();  
        } catch (XMLStreamException e) {  
            e.printStackTrace();  
        }  
    }  
  
}  
View Code

 


測試結果: 

引用
Name=Tom;age=28 
Name=Lily;age=26 
Name=Frank;age=32 
Name=Bob;age=45 
Name=Kate;age=25



EventReaderDelegate的用法與StreamReaderDelegate相同。 

如今咱們介紹完了StAX的兩種解析XML文檔的方式,你們也可能對它的使用有了本身的認識。咱們最後總結一下:XMLStreamReader和XMLEventReader都容許應用程序迭代底層的XML流,區別在於它們如何對外提供解析後的XML信息片斷。前者像個指針,指在剛剛解析過的XML標記的後面,並提供得到關於該標記更多信息的方法。由於不用建立新的對象,因此更節約內存。後者具備更多的面向對象特徵,就是個標準的Java迭代器,解析器的當前狀態反映在事件對象中,應用程序在處理事件對象的時候不須要訪問解析器/讀取器。 

關於各類XML解析技術的優劣 

除了咱們剛剛介紹過的StAX這種Java 6.0新支持的XML文檔解析技術以外,還有四種廣爲應用的解析方式,咱們將對它們作一個簡要介紹,並比較五種技術的優缺點以及性能表現,以供你們在開發中選擇何種解析技術作參考。

1、DOM(Document Object Model) 

文檔對象模型分析方式。以層次結構(相似於樹型)來組織節點和信息片斷,映射XML文檔的結構,容許獲取和操做文檔的任意部分。是W3C的官方標準。 

引用
優勢: 
一、容許應用程序對數據和結構作出更改。 
二、訪問是雙向的,能夠在任什麼時候候在樹中上下導航,獲取和操做任意部分的數據。



引用
缺點: 
一、一般須要加載整個XML文檔來構造層次結構,消耗資源大。



2、SAX(Simple API for XML) 

流模型中的推模型分析方式。經過事件驅動,每發現一個節點就引起一個事件,經過回調方法完成解析工做,解析XML文檔的邏輯須要應用程序完成。 

引用
優勢: 
一、不須要等待全部數據都被處理,分析就能當即開始。 
二、只在讀取數據時檢查數據,不須要保存在內存中。 
三、能夠在某個條件獲得知足時中止解析,沒必要解析整個文檔。 
四、效率和性能較高,能解析大於系統內存的文檔。



引用
缺點: 
一、須要應用程序本身負責TAG的處理邏輯(例如維護父/子關係等),使用麻煩。 
二、單向導航,很難同時訪問同一文檔的不一樣部分數據,不支持XPath。




3、JDOM(Java-based Document Object Model) 

Java特定的文檔對象模型。自身不包含解析器,使用SAX。 

引用
優勢: 
一、使用具體類而不是接口,簡化了DOM的API。 
二、大量使用了Java集合類,方便了Java開發人員。



引用
缺點: 
一、沒有較好的靈活性。 
二、性能較差。



4、DOM4J(Document Object Model for Java) 

簡單易用,採用Java集合框架,並徹底支持DOM、SAX和JAXP。 

引用
優勢: 
一、大量使用了Java集合類,方便Java開發人員,同時提供一些提升性能的替代方法。 
二、支持XPath。 
三、有很好的性能。



引用
缺點: 
一、大量使用了接口,API較爲複雜。



5、StAX(Streaming API for XML) 

流模型中的拉模型分析方式。提供基於指針和基於迭代器兩種方式的支持。 

引用
優勢: 
一、接口簡單,使用方便。 
二、採用流模型分析方式,有較好的性能。



引用
缺點: 
一、單向導航,不支持XPath,很難同時訪問同一文檔的不一樣部分。



爲了比較這五種方式在解析XML文檔時的性能表現,咱們來建立三個不一樣大小的XML文檔:smallusers.xml(100KB)、middleusers.xml(1MB)、bigusers.xml(10MB)。咱們分別用以上五種解析方式對這三個XML進行解析,而後打印出全部的用戶信息,並分別計算它們所用的時間。測試代碼會在文章後面的附件中給出,這裏只比較它們的耗時。 

單位:s(秒) 

                  100KB           1MB            10MB     
DOM              0.146s         0.469s          5.876s    
SAX              0.110s         0.328s          3.547s    
JDOM             0.172s         0.756s          45.447s   
DOM4J            0.161s         0.422s          5.103s    
StAX Stream      0.093s         0.334s          3.553s    
StAX Event       0.131s         0.359s          3.641s    

由上面的測試結果能夠看出,性能表現最好的是SAX,其次是StAX Stream和StAX Event,DOM和DOM4J也有着不錯的表現。性能最差的是JDOM。 因此,若是你的應用程序對性能的要求很高,SAX固然是首選。若是你須要訪問和控制任意數據的功能,DOM是個很好的選擇,而對Java開發人員來說,DOM4J是更好的選擇。 若是隻須要作XML文檔解析的話,綜合性能、易用性、面向對象特徵等各方面來衡量,StAX Event無疑是最好的選擇。 

相關文章
相關標籤/搜索