XML的JAVA 解析(一)(1) (轉)
的 解析(一) microsoft-com::office" />
用SAX 解析XML文件為Java
提要
SAX API在執行中的各方面表現都優於 API。下文將探索用SAX將XML文件解析為Java物件。SAX用起來不像DOM那樣直觀,所以我們要先熟悉一下SAX的用法。 (3,000 字(譯註:英文原文三千字))
Robert Hustead 作
不過,用XML建立系統會有兩個難題:首先,生成XML資料是一個簡單的過程,但是反過來從一個程式裡這些資料就不是了。其次,現今的XML技術都容易被處置不當,這會導致速度慢且消耗大的程式出現。在以XML作為基本資料交換格式的系統中,速度慢和記憶體消耗大被證明是兩個瓶頸。
在當前的各種通用XML處理工具中,有的相對比較好。SAX API就有一些對於要求高的程式碼很有幫助的特點。在這篇文章中,我們要制定一些SAX API編碼。用這種模式,你就能寫出速度快、記憶體消耗小的XML-JAVA對映程式碼,甚至對某些相當複雜的XML結構(但不包括遞迴結構)它們也能應付。
在第二部分,我們將解決含有遞迴的XML結構。這種結構的某些XML元素表示的是一個列表的列表。我們還要開發一個用SAX API處理資料導航的類庫。這個庫可以簡化基於SAX的XML解析。
解析程式碼就像編譯程式碼
寫XML解析程式就像寫一樣。你看,幾乎所有的編譯器都分三步把變為可程式。首先,語法模組將字元組成編譯器能識別的字詞——就是所謂的詞法分析。第二個模組呼叫解析器,分析各組字詞以識別合法的語法。最後的第三個模組處理一系列合法語句結構生成可執行程式碼。有時,源解析和可執行程式碼生成是交織進行的。
要用Java解析XML資料,我們也得經過一個相似的流程。首先我們要分析XML文件中的每個字元以識別合法的XML組成,諸如起始標籤、屬性、結束標籤、CDATA部分。
然後我們證實這些組成可以形成合法的XML結構。如果完全由符合XML 1.0要求的合法結構組成,那麼它就是結構良好的XML文件。比如最基本的,我們要確定所有的標籤都有起始與結束標籤相匹配,同時所有屬生都以正確的形式存在於起始標籤中。
此外,如果有對應的DTD,我們能選擇性地透過驗證解析到的XML結構符合DTD描述,來確定XML文件是結構良好的。
最後,我們用XML文件裡的資料做一些有意義的事情——我管這個叫“XML對映到JAVA物件”(map XML into Java)
XML解析器
幸運的是,有一些現成的——XML解析器——可以用來完成類似編譯的工作。XML解析器處理所有的語法分析和解析。現在的很多基於Java的XML解析器都依照兩種解析標準:SAX和DOM API(譯註:解析器一般會選擇一種標準,並非兩種同時在一個解析器內實現)
有了這些現成的XML解析器好像在Java中使用XML就沒什麼別的困難了,其實使用這些XML解析器也是一件很棘手的事情。
SAX和DOM API
SAX API是基於事件的。實現了SAX API的XML解析器跟據解析到的XML文件的不同特徵產生事件。透過在Java程式碼中捕捉這些事件,就可以寫出由XML資料的程式。
DOM API是一種基於物件的API。實現DOM的XML解析器在記憶體中生成代表XML文件內容的一般物件模型。XML解析器一旦完成解析,記憶體中也就有了一個同時包含XML文件的結構和內容資訊的DOM物件樹。
DOM的概念來自HTML流覽器界,HTML流覽器通常用一個普通文件物件來表示所裝載的HTML檔案。這樣象之類的指令碼語就可以訪問到這些HTML DOM物件。HTML DOM 在這方面的應用是很成功的。
DOM的不足
乍一看,DOM API像有更加豐富的特色,因此也比SAX API更優秀。但是,DOM在用於對效能要求高的時卻存在嚴重的不足。
目前支援DOM的XML解析器都使用一種物件儲存的方式,也就是建立很多小的代表DOM節點的物件,這些節點物件還包含了文字或巢狀其它DOM節點。這看似順理成章,但卻造成了效能的下降。Java中最為影響效能的操作之一是new運算子。對應於new運算子的每一次執行,在對所得物件的所有引用都消失後,垃圾收集器都要負責將這個物件從記憶體中清除。DOM API的眾多小物件一般在解析完後被立即拋棄,這幾乎耗光了JVM的所有記憶體。
DOM的另一個不足是它把整個的XML文件都裝入記憶體。對於大的文件,這就成了一個問題。再一次地,因為DOM是基於許多小物件實現的,所以在儲存XML文件的同時,JVM還要用額外的幾位元組來儲存關於所有這些物件的資訊,這樣一來,記憶體使用就變得比XML文件本身還要大。
還有一個很麻煩的,就是很多Java程式實際上並沒有用到DOM這種一般形式的物件結構。而是在DOM結構一裝入記憶體就立即將資料複製到對應它們的特定問題域的物件結構中——一個繁雜而多餘的過程。
DOM API的另一個不易察覺的問題是,使用它寫成的程式碼要掃描XML文件兩次。第一次將DOM結構讀進記憶體,第二次定位感興趣的資料。理所當然,定位不同的資料塊就要在DOM結構中來回地移動。相反,SAX模式支援一趟同時定位和收集XML資料。
這些問題中有的可以透過設計一個更好的底層資料結構在內部表示DOM物件得以解決。而像使用多次掃描和一般—特定物件模型轉換這樣的問題則無法在XML解析器內部解決。
求肋於SAX
相對DOM API,SAX API是一種頗有吸引力的解決方案。SAX沒有一般的物件模型,所以在記憶體消耗和效能問題上對new運算子的濫用沒有顧忌。同時如果你要設計自己特定問題域的物件模型,SAX也就沒有冗餘的物件模型。並且,SAX一遍就能處理好XML文件,它所需的處理時間大大減少。
SAX確實也有它的不足,但這些不足大都與程式設計師有關,並非API本身的效能問題。我們先來大致看一下。
第一個缺點是概念上的。程式設計師們習慣於透過定位獲取資料。(譯註:作者指程式設計師都喜歡自已主動獲取資料,想要什麼資料就立即去取,而不是SAX這種資料被依次丟擲,再由程式設計師處理的方式。)為了找到上的一個檔案,你透過改變目錄來定位。相似地,為了得到一個裡的資料,你將寫一個查詢語句。對於SAX,這種模式是相反的。也就是,你先建立起自己的程式碼來每列有效的XML資料片。這段程式碼只有當感興趣的XML資料出現時才被呼叫。SAX API乍看起來很彆扭,但是用不了多久,這種思考方式就會成為習慣。
第二個缺點就有點危險了。對於SAX的程式碼,那種天真的草率行事的做法會很快的引火燒身,因為在收集資料的同時也徹底地把XML結構過濾了一遍。大多數的人只注意資料解析而忽視了資料流動是有順序的這一方面。如果你不在自已的程式碼中考慮到資料流將會出現的順序,在進行SAX解析過程中進行定位的程式碼就會發散失控併產生很多複雜的相互耦合(或稱牽制)。這個問題就有點像一般程式中對全域性變數過分依賴所產生的那些問題。但是如果你學會正確地構建優雅的SAX程式碼,它甚到比DOM API還要直觀。(譯註:我在理解這個地方時遇到很大的麻煩,曾直接向Robert求教。反覆閱讀後才明白了一點。SAX解析XML並非沒有資料出現的順序,而是資料出現的順序僅可預測不可改變的,所以在處理資料時要時刻牢記這一點。要構建所謂的優雅的程式碼,我的辦法是不要試圖在收集資料的同時進行過於複雜的操作,不要一心想將已經出現的事件回捲以獲取“從前”的資料。以下是Mr. Robert的答覆:-- The point I'm making is that the navigational aspects of coding a SAX based solution exist whether you are aware of them or not. The fact that they exist will affect how you code. To directly address is to acknowledge the presence and impact of the navigational aspects explicitly during design. The opposite would be to ignore the aspects and instead have the navigational aspects just show up in little pockets of code in unrelated areas of the application.)
基本的SAX
當前SAX API有兩個版本。我們用第二版(見s">資源)來做示例。第二版中的類名和方法名與第一版都有出入,但是程式碼的結構是一樣的。
SAX是一套API,不是一個解析器,所以這個程式碼在XML解析器中是通用的。要讓示例跑起來,你將需要一個支援SAX v2的XML解析器。我用的Xerces解析器。(見)參照你的解析器的getting-started文件來獲得呼叫一個SAX解析器的資料。
SAX API 的說明書通俗易讀。它包含了很多的詳細內容。而使用SAX API的主要任務就是建立一個實現ContentHandler介面,一個供XML 解析器呼叫以將分析XML文件時所發生的SAX事件分發給處理程式的回撥介面。
方便起見,SAX API也提供了一個已經實現了ContentHandler介面的DefaultHandler介面卡類。
一但實現了ContentHandler或者擴充套件了DefaultHandler類,你只需直接將XML解析器解析一個特定的文件即可。
我們的第一個例子擴充套件DefaultHandler將每個SAX事件列印到控制檯。這將給你一個初步的映象,以說明什麼SAX事件將會發生及以怎樣的順序發生。
作為開始,以下是將在我們的第一個示例中用到的XML文件樣本:
接下來,我們看看第一個XML解析例子的程式碼:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
public class Example1 extends DefaultHandler {
// 過載DefaultHandler類的方法
// 以攔截SAX事件通知。
//
// 關於所有有效事件,見org.xml.sax.ContentHandler
//
public void startDocument( ) throws SAXException {
System.out.println( "SAX Event: START DOCUMENT" );
}
public void endDocument( ) throws SAXException {
System.out.println( "SAX Event: END DOCUMENT" );
}
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
System.out.println( "SAX Event: START ELEMENT[ " +
localName + " ]" );
// 如果有屬性,我們也一併列印出來...
for ( int i = 0; i < attr.getLength(); i++ ){
System.out.println( " ATTRIBUTE: " +
attr.getLocalName(i) +
" VALUE: " +
attr.getValue(i) );
}
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
System.out.println( "SAX Event: END ELEMENT[ " +
localName + " ]" );
}
public void characters( char[] ch, int start, int length )
throws SAXException {
System.out.print( "SAX Event: CHARACTERS[ " ];
try {
OutputStreamWriter outw = new OutputStreamWriter(System.out);
outw.write( ch, start,length );
outw.flush();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( " )" );
}
public static void main( String[] argv ){
System.out.println( "Example1 SAX Events:" );
try {
// 建立SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// ContentHandler...
xr.setContentHandler( new Example1() );
// 解析檔案...
xr.parse( new InputSource(
new FileReader( "Example1.xml" )) );
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
最後,就得到了執行第一個例子解析我們的XML樣本文件所產生的輸出:
Example1 SAX Events:
SAX Event: START DOCUMENT
SAX Event: START ELEMENT[ simple ]
ATTRIBUTE: date VALUE: 7/7/2000
SAX Event: CHARACTERS[
]
SAX Event: START ELEMENT[ name ]
SAX Event: CHARACTERS[ Bob ]
SAX Event: END ELEMENT[ name ]
SAX Event: CHARACTERS[
]
SAX Event: START ELEMENT[ location ]
SAX Event: CHARACTERS[ New York ]
SAX Event: END ELEMENT[ location ]
SAX Event: CHARACTERS[
]
SAX Event: END ELEMENT[ simple ]
SAX Event: END DOCUMENT
如你所見,SAX解析器會為每個在XML文件中出現的SAX事件呼叫正確的ContentHandler成員方法。
(未完待續)
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1003020/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java解析XMLJavaXML
- 使用 Java 解析XML檔案JavaXML
- Java 將HTML轉為XMLJavaHTMLXML
- java的XML解析(DOM4J技術)JavaXML
- java 物件與xml相互轉換Java物件XML
- Xml解析XML
- Java學習--xml文字轉換成Java物件JavaXML物件
- java使用jaxb解析XML(含根據xml自動生成實體類)JavaXML
- go 解析xmlGoXML
- iOS – XML解析iOSXML
- DOM4J 解析 XML 之忽略轉義字元XML字元
- XML解析-最快的方式SAXXML
- 曹工說Tomcat1:從XML解析說起TomcatXML
- Java設計模式模式 (包括工廠模式xml解析)Java設計模式XML
- java EE開發之Servlet第五課:xml解析JavaServletXML
- Java 解析xml報文放入Map,並判斷所有xml標籤是否為空JavaXML
- PHP xml 轉陣列 陣列轉 xml 操作PHPXML陣列
- python之XML解析PythonXML
- 175.XML解析XML
- C# XML解析C#XML
- Java XML和JSON:Java SE的文件處理,第1部分JavaXMLJSON
- Java中將XML轉換為PDF的兩種辦法JavaXML
- Spring原始碼分析(一)Spring的初始化和XML解析Spring原始碼XML
- Python XML解析之DOMPythonXML
- python XML 檔案解析PythonXML
- jdom解析xml檔案XML
- XML 檔案解析實踐 (DOM 解析)XML
- Java Spring Beans.xml裡的Bean定義是如何被解析出來的JavaSpringBeanXML
- Android逆向(一) —— AndroidManifest.xml 二進位制解析AndroidXML
- 使用DocumentBuilderFactory解析XML淺談UIXML
- XML DOM 解析器概述XML
- 基於 DOM 的 XML 檔案解析類XML
- xml字串轉JSON字串XML字串JSON
- C# 操作xml(轉)C#XML
- Java解析xml檔案遇到特殊符號&會出現異常的解決方案JavaXML符號
- namespace對axis解析xml請求的影響namespaceXML
- Python解析XML檔案生成HTMLPythonXMLHTML
- 搞懂 XML 解析,徒手造 WEB 框架XMLWeb框架
- 【spring原始碼系列】之【xml解析】Spring原始碼XML