轉自:http://www.ibm.com/developerworks/cn/opensource/os-ag-renegade15/
XML 的重要性
XML 是由 Tim Bray 和 Michael Sperberg-McQueen 於 1996 年引入的。它的潛力已獲得廣泛公認,但是很難想象那時候會有哪個人能夠知道 XML 會成為怎樣的一種主要技術。企業 Java 開發人員將使用 XML 用於配置、用作資料存放區以及最常見的是用作資料交換的格式。它是 Web 服務和 SOAP 的基礎,從而也是現代面向服務的架構(Service-Oriented Architecture,SOA)設計模式的基礎。但是 XML 並沒有止步在那裡。它將 X 融入 Ajax 或 Asynchronous JavaScript + XML 中,成為現代 Web 應用程式能夠提供前所未有的豐富體驗的關鍵。
但是,XML 也不是包治百病的靈丹妙藥;它也有不足之處。XML 文檔往往都很大。XML 文檔都有通用樹結構,但是這些 XML 文檔的可擴充性意味著它的模式可以千變萬化。這些方面向高效解析 XML 提出了挑戰。克服 XML 解析挑戰的傳統方法有兩種:DOM 和 SAX。
XML 處理:DOM 和 SAX
DOM 和 SAX 是解析 XML 的兩種典型策略。它們在許多方面都是性質對立的策略。DOM 將為 XML 文檔提供一個簡單的物件模型。DOM 解析器將把 XML 文檔轉換成表示文檔中所有資料的便於使用的對象。但是,這樣如實地表示 XML 文檔需要付出一定的代價:DOM 解析往往需要佔用很多記憶體。
記憶體對 SAX 來說不是問題。SAX 解析器將產生一系列解析事件。註冊這些事件的回調並隨後對來自這些事件的資料執行某種邏輯都由 handler 來完成。它快速且高效,但是要求有複雜的編程模型。
瞭解使用 DOM 與 SAX 之間差異的最簡單方法 —— 並且因而瞭解 StAX 的動機和優點 —— 是查看具體樣本。
使用 Flickr 解析樣本
找到一些 XML 來解析並不難。到處都在使用 XML。現在的大多數 Web 網站都提供了某種基於 XML 的 Web 服務。Flickr 是歸 Yahoo 所有的一個流行的照片分享網站,它擁有強大而靈活的 API。讓我們來看一看訪問 Flickr 的 “有趣” 照片的一些簡單代碼(要獲得本文中使用的所有原始碼,請參閱 下載,並確保把 Woodstox 放入類路徑中或者使用 JDK 1.6)。代碼如清單 1 所示:
清單 1. 使用 Flickr API
String apiKey = "c4579586f41a90372f762cb65c78be5d";String urlStr = "http://api.flickr.com/services/rest/?" + "method=flickr.interestingness.getList&per_page=20&api_key="+apiKey;URL request = new URL(urlStr);InputStream input = request.openStream(); |
這段代碼將使用 Flickr 的代表性狀態傳輸(Representational State Transfer,REST)API(有關 Flickr 的 API 和 REST 格式的更多資訊,請參閱 參考資料 部分)。以上調用的一些範例輸出如清單 2 所示:
清單 2. Flickr 的 XML
<?xml version="1.0" encoding="utf-8" ?><rsp stat="ok"><photos page="1" pages="25" per_page="20" total="500"> <photo id="469774979" owner="35373726@N00" secret="c8a1be2012" server="183" farm="1" title="Where will it lead me......?" ispublic="1" isfriend="0" isfamily="0" /> <photo id="470281793" owner="73955226@N00" secret="49612a2794" server="212" farm="1" title="Island Beauty" ispublic="1" isfriend="0" isfamily="0" /> <photo id="469808244" owner="43568064@N00" secret="26b71544a3" server="227" farm="1" title="" ispublic="1" isfriend="0" isfamily="0" /></photos></rsp> |
注意清單 2 只顯示了三張照片。API 呼叫實際上將返回 20(URL 字串中的 per_page
參數)。結果十分簡單,因此來看一看如何解析這個 XML。在樣本中,將解析出每張照片的標題及其 ID。該 ID 可用於建立該照片的 URL,因此不難想象 Web 應用程式(可能是 mashup)只使用此資訊。首先需要使用 DOM 來提取此資料。
DOM 樣本
要使用 DOM,需要把文檔解析成文檔對象。這是表示已被解析的 XML 文檔的記憶體中樹結構。隨後遍曆 DOM 樹來尋找每張照片的標題和 ID。將此資料放入簡易對應中。完成此過程的代碼如清單 3 所示:
清單 3. 用 DOM 進行解析
Map<String,String> map = new HashMap<String,String>();DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();Document dom = builder.parse(input);Element root = dom.getDocumentElement();NodeList childNodes = root.getChildNodes();Node photosNode = null;for (int i=0;i<childNodes.getLength();i++){ Node node = childNodes.item(i); if (node.getNodeName().equalsIgnoreCase("photos")){ photosNode = node; break; }}childNodes = photosNode.getChildNodes();for (int i=0;i<childNodes.getLength();i++){ Node node = childNodes.item(i); if (node.getNodeName().equalsIgnoreCase("photo")){ String title = node.getAttributes().getNamedItem("title").getTextContent(); String id = node.getAttributes().getNamedItem("id").getTextContent(); map.put(id,title); }} |
DOM 十分流行,因為它非常便於使用。只需將輸入源傳入解析器中,然後解析器將為您提供 document
對象。然後,您可以遍曆子節點,直至找到照片節點。每個照片節點都是照片節點的子節點,因此您將遍曆每個照片節點,然後訪問每個照片節點的 title
和 id
屬性並將其儲存到映射中。
但是,DOM 也有一些明顯的效率低下之處。您要儲存大量的可能並不關心的資料,例如每張照片的所有者。您還將瀏覽所有資料兩次:第一次瀏覽用於將其讀入文檔對象,然後在遍曆文檔對象時進行第二次瀏覽。避免這些效率低下之處的傳統方法是使用 SAX。
SAX 樣本
SAX 解析器並不像 DOM 解析器一樣返回一個精密的 document
對象。相反,SAX 解析器將在遍曆 XML 文檔時給出一系列事件。必須通過實現介面或者擴充 DefaultHandler
類並根據需要重寫它的方法,建立這些事件的 handler。清單 4 將示範 Flickr XML 文檔的 SAX 解析。
清單 4. 用 SAX 進行解析
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();DefaultHandler handler = new DefaultHandler(){ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("photo")){ String title = attributes.getValue("title"); String id = attributes.getValue("id"); // map is static so we can access it here map.put(id, title); } }};parser.parse(input, handler); |
很明顯,清單 4 中所示的代碼比 清單 3 中的 DOM 代碼難於理解一些。您需要使用 ContentHandler
來處理 SAX 事件,因此建立了 DefaultHandler
並重寫了它的 startElement
回調方法。查看它是不是一個照片元素,並且如果是照片元素,則訪問它的 title
和 id
屬性。
代碼十分簡潔並且在運行時非常高效。它僅儲存您所關心的資料,並且只遍曆文檔一次。它是更為複雜的代碼,要求擴充類才能註冊事件偵聽程式。如果能夠高效地解析 XML 而且可以使用更直觀的編程模型那就太好了。StAX 於是應運而生。
StAX 備選方案
SAX 中的複雜度來自它實現的 Observer 設計模式。它是一個 push 模型,因為解析器將把事件 push 到隨後作用於事件的 observer 中。StAX 模型類似於 SAX。它將來自 XML 文檔的資料和事件流線化,使其可以像 SAX 一樣快速且高效。最大的不同之處是它使用 pull 模型。這將允許應用程式代碼從解析器中 pull 事件。
這可能聽起來像是一個細微的差異,但是它將允許使用更簡單的編程模型。查閱清單 5 以查看 StAX 的運作。
清單 5. 用 StAX 進行解析
Map<String,String> map = new HashMap<String,String>();XMLInputFactory inputFactory = XMLInputFactory.newInstance();QName qId = new QName("id");QName qTitle = new QName("title");QName qPhoto = new QName("photo");XMLEventReader reader = inputFactory.createXMLEventReader(input);while (reader.hasNext()){ XMLEvent event = reader.nextEvent(); if (event.isStartElement()){ StartElement element = event.asStartElement(); if (element.getName().equals(qPhoto)){ String id = element.getAttributeByName(qId).getValue(); String title = element.getAttributeByName(qTitle).getValue(); map.put(id,title); } }}reader.close(); |
首先,您不必擴充任何類。那是因為您無需為事件進行註冊。使用 StAX,您可以控制事件流,因為將從解析器中 pull 這些事件流。您可以使用一種熟悉的類似迭代器的文法來搜尋整個文檔以尋找所需的資料。您仍將只儲存所需的資料,並且只需瀏覽 XML 文檔一次。您將獲得與使用 SAX 時一樣的效率,但是代碼將直觀得多。
使用 Woodstox 作為 Geronimo 的 StAX 提供者
現在您已經看到了 StAX 解析的優點。它被廣泛公認為 XML 技術中的重大進步。因而,當它成為 Java EE 5 規範的一部分時並不令人驚訝(它甚至還包含在 Java Platform, Standard Edition [Java SE] 6 中)。由於它是 Java EE 5 的一部分,因此它必須由 Geronimo 2.0 來實現。
Geronimo 團隊十分幸運,有若干個開源 StAX 實現可供選擇。團隊選取了 Woodstox 作為 Geronimo 所附帶的 StAX 解析器。Woodstox 被視為執行效果最佳的 StAX 實現之一(要比較各種 StAX 解析器,請參閱 參考資料)。此外,Woodstox 是在 Lesser General Public License (LGPL) 和 Apache 2.0 許可下雙重授權的。因此您可以不受任何限制地將 Woodstox 及其原始碼整合到 Geronimo 中。
應用程式效能調優:發揮 Woodstox 的最大功效
效能很明顯是 Woodstox 帶給 Geronimo 的優點之一。就像使用其他高效能技術一樣,瞭解如何使用 Woodstox 才能獲得最佳效能非常重要。清單 5 中的代碼將使用 XMLEventReader
介面,這是 StAX 規範包含的一個進階 API。用於獲得高效能的較低級 API 是 XMLStreamReader
介面。清單 6 顯示了使用此介面的 StAX 解析器。
清單 6. 用 XMLStreamReader
進行 StAX 解析
Map<String,String> map = new HashMap<String,String>();XMLInputFactory inputFactory = XMLInputFactory.newInstance();QName qId = new QName("id");QName qTitle = new QName("title");QName qPhoto = new QName("photo");XMLStreamReader reader = inputFactory.createXMLStreamReader(input);while (reader.hasNext()){ int event = reader.next(); if (event == START_ELEMENT){ // statically included constant from XMLStreamConstants if (reader.getName().equals(qPhoto)){ String id = reader.getAttributeValue(null, qId.getLocalPart()); String title = reader.getAttributeValue(null, qTitle.getLocalPart()); map.put(id,title); } } }reader.close(); |
清單 6 中的代碼類似於 清單 5 中的代碼;雖然它很明顯有些低級,但是您將獲得很大的效能提高。
結束語
您已經瞭解了使用 StAX 解析器解析 XML 文檔的一些優點。StAX 在 SAX 與 DOM 之間提供了很好的折衷。您可以通過使用它作為 Geronimo 2.0 的一部分立即利用 StAX。您不但將開始使用 StAX 的直觀 pull API,而且將獲得在 Woodstox 中使用 StAX 的高效能實現的額外優勢。