博文引自: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以前,就已經有四種: 網絡
關於它們的解析原理,以及性能和優缺點,我會在本文的結尾作一個簡要的介紹。這篇文章中,咱們主要說說StAX這種新的解析方式。
首先咱們來搞清楚兩個概念:推分析和拉分析。
在程序中訪問和操做XML文件通常有兩種模型:DOM(文檔對象模型)和流模型。它們的優缺點以下:
架構
關於什麼是DOM,文章結尾處會有介紹。這裏咱們簡單說一下流:它是一個連續的字節序列,能夠理解爲不停地從源頭向目標搬運着字節的特殊對象。
讓咱們回到主題。流模型每次迭代XML文檔中的一個節點,適合於處理較大的文檔,所耗內存空間小。它有兩種變體--「推」模型和「拉」模型。
框架
到此,咱們就弄明白了「推分析」和「拉分析」的概念:
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有: 模塊化
JWSDP的早期版本中還包括: 性能
如今,JWSDP已經被GlassFish所替代。
StAX包括兩套處理XML的API,分別提供了不一樣程度的抽象。它們是:基於指針的API和基於迭代器的API。
咱們先來了解基於指針的API。它把XML做爲一個標記(或事件)流來處理,應用程序能夠檢查解析器的狀態,得到解析的上一個標記的信息,而後再處理下一個標記,依次類推。
在開始API探索以前,咱們首先建立一個名爲users.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
或者:
編碼
這兩個方法是等價的,它們都是建立了一個新的實例,甚至實例的類型都是徹底一致的。由於它們的內部實現都是:
{ return (XMLInputFactory) FactoryFinder.find("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl"); }
接下來咱們就能夠建立XMLStreamReader實例了。咱們有這樣一組方法能夠選擇:
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是間接定位外部資源。具體一點說是這樣:
好了,咱們接着用以上列出的第一個接口來建立一個XMLStreamReader實例:
try { XMLStreamReader reader = factory.createXMLStreamReader(new FileReader("users.xml")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); }
要遍歷XML文檔,須要用到XMLStreamReader的下面幾個方法:
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文檔作一個測試。但願你還記得它的內容,若是忘記了,請翻回去從新瀏覽一下。
咱們的測試代碼以下:
/** * 列出全部用戶 * * @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(); } }
運行結果:
在上面的示例代碼中,咱們用到了XMLStreamReader的兩個新方法:
String getLocalName();
String getAttributeValue(String namespaceURI, String localName);
與此相關的還有一個方法:
QName getName();
這三個方法牽扯到XML的namespace(命名空間)、localName(本地名稱)、QName(Qualified Name,限定名稱)三個概念,咱們順便解釋一下:
命名空間是爲了支持相同名稱不一樣含義的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(統一資源名稱)兩種。前綴是命名空間的簡寫,目的是爲了使用方便。命名空間被聲明後就能夠被使用:
<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文檔,並對特定標籤進行解析了。
咱們再來看看下面兩個方法:
String getElementText() throws XMLStreamException; int nextTag() throws XMLStreamException;
getElementText()方法返回元素的開始標籤(START_ELEMENT)和關閉標籤(END_ELEMENT)之間的全部文本內容,若遇到嵌套的元素就會拋出異常。
nextTag()方法將跳過全部空白、註釋或處理指令,直到遇到START_ELEMENT或END_ELEMENT。它在解析只含元素內容的XML文檔時頗有用。不然,在發現標記以前遇到非空白文本(不包括註釋和處理指令),就會拋出異常。
好比咱們修改上一個測試程序,增長一個新方法:
// 列出全部用戶的名稱和年齡 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(); } }
而後把它添加到主方法中:
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的某些方法,不管當前標記(或事件)是什麼類型的,均可以被調用。它們的定義和做用以下:
對於以上方法都很容易理解和記憶,咱們再也不編寫代碼展現它們的效果。
讓咱們看看有關屬性操做方法。仍是首先熟悉一下它們的定義:
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);
這些方法都十分容易理解,基本上看方法的名稱和參數就知道它的用途了。並且最後一個方法在上面的示例中咱們已經用過了。讓咱們再用一個簡單的示例程序進一步加深對這些方法的認識。
// 列出全部用戶的名稱和年齡 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(); } }
把它加入到主方法中:
public static void main(String[] args) { ListUsers.listNames(); // ListUsers.listNamesAndAges(); ListUsers.listAllAttrs(); }
運行結果:
相信你看到這裏,已經能夠順利地使用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實例的方法:
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接口,它定義瞭如下幾個方法:
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接口中定義的方法。這些方法大致能夠分爲三種類別。第一類是用於事件類型判斷的:
第二類是將XMLEvent轉換爲具體的子類對象的:
第三類是獲取事件對象通用信息的:
javax.xml.stream.Location getLocation();//得到事件對象的位置信息,相似於XMLStreamReader的getLocation()方法 int getEventType();//得到事件對象的類型,相似於XMLStreamReader的getEventType()方法
其中,getEventType()方法的返回值也是XMLStreamConstants中定義的常量,其類型和含義與XMLStreamReader的getEventType()方法的返回值徹底相同。
下面讓咱們用一段示例代碼來熟悉基於迭代器的StAX API的使用方法,進而引出XMLEvent接口的子接口類型。咱們仍然使用users.xml做爲測試文件:
// 列出全部信息 @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(); } }
把它加到主程序中:
public static void main(String[] args) { ListUsers.listNames(); // ListUsers.listNamesAndAges(); ListUsers.listAllAttrs(); ListUsers.listAllByXMLEventReader(); }
運行後獲得以下結果:
這個例子中,咱們利用基於迭代器的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還有兩個建立流讀取器的方法:
XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) throws XMLStreamException; XMLEventReader createFilteredReader(XMLEventReader reader, EventFilter filter) throws XMLStreamException;
它們分別爲XMLStreamReader和XMLEventReader增長一個過濾器,過濾掉不須要解析的內容,只留下應用程序關心的信息用於解析。雖然咱們能夠在應用程序中作一樣的過濾工做,就像以前示例程序中所寫的那樣,可是把過濾工做交給過濾器的好處是,讓應用程序能夠更加專一於解析工做,而且對於通用的過濾(好比註釋),將它放到過濾器中能夠實現過濾邏輯部分代碼的重用。這符合軟件設計原則。
若是你編寫過文件過濾器java.io.FileFilter的話,那麼編寫StreamFilter和EventFilter就更加容易。咱們先來看看這兩個接口的定義:
public interface StreamFilter { public boolean accept(XMLStreamReader reader); } public interface EventFilter { public boolean accept(XMLEvent event); }
咱們就以StreamFilter爲例來演示過濾器的用法。爲此,咱們使用users.xml爲測試文檔編寫一段新的程序:
/** * 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(); } } }
測試結果:
你們可能已經發現,這裏有一個與以前處理不一樣的地方,就是咱們先打印了用戶的信息,再調用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增長新的功能。請看下面的例子:
/** * 測試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(); } } }
測試結果:
EventReaderDelegate的用法與StreamReaderDelegate相同。
如今咱們介紹完了StAX的兩種解析XML文檔的方式,你們也可能對它的使用有了本身的認識。咱們最後總結一下:XMLStreamReader和XMLEventReader都容許應用程序迭代底層的XML流,區別在於它們如何對外提供解析後的XML信息片斷。前者像個指針,指在剛剛解析過的XML標記的後面,並提供得到關於該標記更多信息的方法。由於不用建立新的對象,因此更節約內存。後者具備更多的面向對象特徵,就是個標準的Java迭代器,解析器的當前狀態反映在事件對象中,應用程序在處理事件對象的時候不須要訪問解析器/讀取器。
關於各類XML解析技術的優劣
除了咱們剛剛介紹過的StAX這種Java 6.0新支持的XML文檔解析技術以外,還有四種廣爲應用的解析方式,咱們將對它們作一個簡要介紹,並比較五種技術的優缺點以及性能表現,以供你們在開發中選擇何種解析技術作參考。
1、DOM(Document Object Model)
文檔對象模型分析方式。以層次結構(相似於樹型)來組織節點和信息片斷,映射XML文檔的結構,容許獲取和操做文檔的任意部分。是W3C的官方標準。
2、SAX(Simple API for XML)
流模型中的推模型分析方式。經過事件驅動,每發現一個節點就引起一個事件,經過回調方法完成解析工做,解析XML文檔的邏輯須要應用程序完成。
3、JDOM(Java-based Document Object Model)
Java特定的文檔對象模型。自身不包含解析器,使用SAX。
4、DOM4J(Document Object Model for Java)
簡單易用,採用Java集合框架,並徹底支持DOM、SAX和JAXP。
5、StAX(Streaming API for XML)
流模型中的拉模型分析方式。提供基於指針和基於迭代器兩種方式的支持。
爲了比較這五種方式在解析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無疑是最好的選擇。