kapok

          垃圾桶,嘿嘿,我藏的這么深你們還能找到啊,真牛!

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            455 隨筆 :: 0 文章 :: 76 評(píng)論 :: 0 Trackbacks
          http://www0.ccidnet.com/tech/guide/2001/10/08/58_3392.html
          SAX概念

          SAX是Simple API for XML的縮寫(xiě),它并不是由W3C官方所提出的標(biāo)準(zhǔn),可以說(shuō)是“民間”的事實(shí)標(biāo)準(zhǔn)。實(shí)際上,它是一種社區(qū)性質(zhì)的討論產(chǎn)物。雖然如此,在XML中對(duì)SAX的應(yīng)用絲毫不比DOM少,幾乎所有的XML解析器都會(huì)支持它。

          與DOM比較而言,SAX是一種輕量型的方法。我們知道,在處理DOM的時(shí)候,我們需要讀入整個(gè)的XML文檔,然后在內(nèi)存中創(chuàng)建DOM樹(shù),生成DOM樹(shù)上的每個(gè)Node對(duì)象。當(dāng)文檔比較小的時(shí)候,這不會(huì)造成什么問(wèn)題,但是一旦文檔大起來(lái),處理DOM就會(huì)變得相當(dāng)費(fèi)時(shí)費(fèi)力。特別是其對(duì)于內(nèi)存的需求,也將是成倍的增長(zhǎng),以至于在某些應(yīng)用中使用DOM是一件很不劃算的事(比如在applet中)。這時(shí)候,一個(gè)較好的替代解決方法就是SAX。

          SAX在概念上與DOM完全不同。首先,不同于DOM的文檔驅(qū)動(dòng),它是事件驅(qū)動(dòng)的,也就是說(shuō),它并不需要讀入整個(gè)文檔,而文檔的讀入過(guò)程也就是SAX的解析過(guò)程。所謂事件驅(qū)動(dòng),是指一種基于回調(diào)(callback)機(jī)制的程序運(yùn)行方法。(如果你對(duì)Java新的代理事件模型比較清楚的話,就會(huì)很容易理解這種機(jī)制了)


          點(diǎn)擊可放大

          在XMLReader接受XML文檔,在讀入XML文檔的過(guò)程中就進(jìn)行解析,也就是說(shuō)讀入文檔的過(guò)程和解析的過(guò)程是同時(shí)進(jìn)行的,這和DOM區(qū)別很大。解析開(kāi)始之前,需要向XMLReader注冊(cè)一個(gè)ContentHandler,也就是相當(dāng)于一個(gè)事件監(jiān)聽(tīng)器,在ContentHandler中定義了很多方法,比如startDocument(),它定制了當(dāng)在解析過(guò)程中,遇到文檔開(kāi)始時(shí)應(yīng)該處理的事情。當(dāng)XMLReader讀到合適的內(nèi)容,就會(huì)拋出相應(yīng)的事件,并把這個(gè)事件的處理權(quán)代理給ContentHandler,調(diào)用其相應(yīng)的方法進(jìn)行響應(yīng)。

          這樣泛泛的說(shuō)來(lái)或許有些不容易理解,別急,后面的例子會(huì)讓你明白SAX的解析過(guò)程。看看這個(gè)簡(jiǎn)單XML文件:

          <POEM>
          <AUTHOR>Ogden Nash</AUTHOR>
          <TITLE>Fleas</TITLE>
          <LINE>Adam</LINE>
          </POEM>

          當(dāng)XMLReader讀到<POEM>標(biāo)簽時(shí),就會(huì)調(diào)用ContentHandler.startElement()方法,并把標(biāo)簽名POEM作為參數(shù)傳遞過(guò)去。在你實(shí)現(xiàn)的startElement()方法中需要做相應(yīng)的動(dòng)作,以處理當(dāng)<POEM>出現(xiàn)時(shí)應(yīng)該做的事情。各個(gè)事件隨著解析的過(guò)程(也就是文檔讀入的過(guò)程)一個(gè)個(gè)順序的被拋出,相應(yīng)的方法也會(huì)被順序的調(diào)用,最后,當(dāng)解析完成,方法都被調(diào)用后,對(duì)文檔的處理也就完成了。下面的這個(gè)表,列出了在解析上面的那個(gè)XML文件的時(shí)候,順序被調(diào)用的方法:

          遇到的項(xiàng)目
          方法回調(diào)
          {文檔開(kāi)始} startDocument()
          <POEM> startElement(null,"POEM",null,{Attributes})
          "\n" characters("<POEM>\n...", 6, 1)
          <AUTHOR> startElement(null,"AUTHOR",null,{Attributes})
          "Ogden Nash" characters("<POEM>\n...", 15, 10)
          </AUTHOR> endElement(null,"AUTHOR",null)
          "\n" characters("<POEM>\n...", 34, 1)
          <TITLE> startElement(null,"TITLE",null,{Attributes})
          "Fleas" characters("<POEM>\n...", 42, 5)
          </TITLE> endElement(null,"TITLE",null)
          "\n" characters("<POEM>\n...", 55, 1)
          <LINE> startElement(null,"LINE",null,{Attributes})
          "Adam" characters("<POEM>\n...", 62, 4)
          </LINE> endElement(null,"LINE",null)
          "\n" characters("<POEM>\n...", 67, 1)
          </POEM> endElement(null,"POEM",null)
          {文檔結(jié)束} endDocument()

          ContentHandler實(shí)際上是一個(gè)接口,當(dāng)處理特定的XML文件的時(shí)候,就需要為其創(chuàng)建一個(gè)實(shí)現(xiàn)了ContentHandler的類來(lái)處理特定的事件,可以說(shuō),這個(gè)實(shí)際上就是SAX處理XML文件的核心。下面我們來(lái)看看定義在其中的一些方法:

          void characters(char[] ch, int start, int length):

          這個(gè)方法用來(lái)處理在XML文件中讀到字符串,它的參數(shù)是一個(gè)字符數(shù)組,以及讀到的這個(gè)字符串在這個(gè)數(shù)組中的起始位置和長(zhǎng)度,我們可以很容易的用String類的一個(gè)構(gòu)造方法來(lái)獲得這個(gè)字符串的String類:String charEncontered=new String(ch,start,length)。

          void startDocument():

          當(dāng)遇到文檔的開(kāi)頭的時(shí)候,調(diào)用這個(gè)方法,可以在其中做一些預(yù)處理的工作。

          void endDocument():

          和上面的方法相對(duì)應(yīng),當(dāng)文檔結(jié)束的時(shí)候,調(diào)用這個(gè)方法,可以在其中做一些善后的工作。

          void startElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName, Attributes atts)

          當(dāng)讀到一個(gè)開(kāi)始標(biāo)簽的時(shí)候,會(huì)觸發(fā)這個(gè)方法。在SAX1.0版本中并不支持名域,而在新的2.0版本中提供了對(duì)名域的支持,這兒參數(shù)中的namespaceURI就是名域,localName是標(biāo)簽名,qName是標(biāo)簽的修飾前綴,當(dāng)沒(méi)有使用名域的時(shí)候,這兩個(gè)參數(shù)都未null。而atts是這個(gè)標(biāo)簽所包含的屬性列表。通過(guò)atts,可以得到所有的屬性名和相應(yīng)的值。要注意的是SAX中一個(gè)重要的特點(diǎn)就是它的流式處理,在遇到一個(gè)標(biāo)簽的時(shí)候,它并不會(huì)紀(jì)錄下以前所碰到的標(biāo)簽,也就是說(shuō),在startElement()方法中,所有你所知道的信息,就是標(biāo)簽的名字和屬性,至于標(biāo)簽的嵌套結(jié)構(gòu),上層標(biāo)簽的名字,是否有子元屬等等其它與結(jié)構(gòu)相關(guān)的信息,都是不得而知的,都需要你的程序來(lái)完成。這使得SAX在編程處理上沒(méi)有DOM來(lái)得那么方便。

          void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName)

          這個(gè)方法和上面的方法相對(duì)應(yīng),在遇到結(jié)束標(biāo)簽的時(shí)候,調(diào)用這個(gè)方法。

          因?yàn)镃ontentHandler是一個(gè)接口,在使用的時(shí)候可能會(huì)有些不方便,因而,SAX中還為其制定了一個(gè)Helper類:DefaultHandler,它實(shí)現(xiàn)了這個(gè)接口,但是其所有的方法體都為空,在實(shí)現(xiàn)的時(shí)候,你只需要繼承這個(gè)類,然后重載相應(yīng)的方法即可。

          OK,到這兒SAX的基本知識(shí)已經(jīng)差不多講完了,下面我們來(lái)看看兩個(gè)具體的例子,以更好的理解SAX地用法。

          SAX編程實(shí)例
          我們還是沿用講DOM的時(shí)候使用的那個(gè)文檔例子,但首先,我們先看一個(gè)簡(jiǎn)單一些的應(yīng)用,我們希望能夠統(tǒng)計(jì)一下XML文件中各個(gè)標(biāo)簽出現(xiàn)的次數(shù)。這個(gè)例子很簡(jiǎn)單,但是足以闡述SAX編程的基本思路了。

          一開(kāi)始當(dāng)然還是import語(yǔ)句了:

          import org.xml.sax.helpers.DefaultHandler;
          import javax.xml.parsers.*;
          import org.xml.sax.*;
          import org.xml.sax.helpers.*;
          import java.util.*;
          import java.io.*;

          然后,我們創(chuàng)建一個(gè)繼承于DefaultHandler的類,具體的程序邏輯在這兒可以暫且放在一邊,要注意的是程序的結(jié)構(gòu):

          public class SAXCounter extends DefaultHandler {
          private Hashtable tags; //這個(gè)Hashtable用來(lái)記錄tag出現(xiàn)的次數(shù)
          // 處理文檔前的工作
          public void startDocument() throws SAXException {
          tags = new Hashtable();//初始化Hashtable
          }
          //對(duì)每一個(gè)開(kāi)始元屬進(jìn)行處理
          public void startElement(String namespaceURI, String localName,
          String rawName, Attributes atts)
          throws SAXException
          {
          String key = localName;
          Object value = tags.get(key);
          if (value == null) {
          // 如果是新碰到的標(biāo)簽,這在Hastable中添加一條記錄
          tags.put(key, new Integer(1));
          } else {
          // 如果以前碰到過(guò),得到其計(jì)數(shù)值,并加1
          int count = ((Integer)value).intValue();
          count++;
          tags.put(key, new Integer(count));
          }
          }
          //解析完成后的統(tǒng)計(jì)工作
          public void endDocument() throws SAXException {
          Enumeration e = tags.keys();
          while (e.hasMoreElements()) {
          String tag = (String)e.nextElement();
          int count = ((Integer)tags.get(tag)).intValue();
          System.out.println("Tag <" + tag + "> occurs " + count
          + " times");
          }
          }
          //程序入口,用來(lái)完成解析工作
          static public void main(String[] args) {
          String filename = null;
          boolean validation = false;
          filename="links.xml";
          SAXParserFactory spf = SAXParserFactory.newInstance();
          XMLReader xmlReader = null;
          SAXParser saxParser=null;
          try {
          // 創(chuàng)建一個(gè)解析器SAXParser對(duì)象
          saxParser = spf.newSAXParser();
          // 得到SAXParser中封裝的SAX XMLReader
          xmlReader = saxParser.getXMLReader();
          } catch (Exception ex) {
          System.err.println(ex);
          System.exit(1);
          }
          try {
          //使用指定的ContentHandler,解析給XML文件,這兒要注意的是,為了
          //程序的簡(jiǎn)單起見(jiàn),這兒將主程序和ContentHandler放在了一起。實(shí)際上
          //main方法中所作的所有事情,都與ContentHandler無(wú)關(guān)。
          xmlReader.parse(new File(filename),new SAXCounter());
          } catch (SAXException se) {
          System.err.println(se.getMessage());
          System.exit(1);
          } catch (IOException ioe) {
          System.err.println(ioe);
          System.exit(1);
          }
          }
          }

          我們來(lái)看看這段程序作了些什么,在main()方法中,主要做的就是創(chuàng)建解析器,然后解析文檔。實(shí)際上,在這兒創(chuàng)建SAXParser對(duì)象的時(shí)候,為了使程序代碼于具體的解析器無(wú)關(guān),使用了同DOM中一樣的設(shè)計(jì)技巧:通過(guò)一個(gè)SAXParserFactory類來(lái)創(chuàng)建具體的SAXParser對(duì)象,這樣,當(dāng)需要使用不同的解析器的時(shí)候,要改變的,只是一個(gè)環(huán)境變量的值,而程序的代碼可以保持不變。這就是FactoryMethod模式的思想。在這兒不再具體講了,如果還有不明白的,可以參看上面DOM中的解釋,原理是一樣的。

          不過(guò)在這兒還有一點(diǎn)點(diǎn)要注意的地方,就是SAXParser類和XMLReader類之間的關(guān)系。你可能有些迷糊了吧,實(shí)際上SAXParser是JAXP中對(duì)XMLReader的一個(gè)封裝類,而XMLReader是定義在SAX2.0種的一個(gè)用來(lái)解析文檔的接口。你可以同樣的調(diào)用SAXParser或者XMLReader中的parser()方法來(lái)解析文檔,效果是完全一樣的。不過(guò)在SAXParser中的parser()方法接受更多的參數(shù),可以對(duì)不同的XML文檔數(shù)據(jù)源進(jìn)行解析,因而使用起來(lái)要比XMLReader要方便一些。

          這個(gè)例子僅僅涉及了SAX的一點(diǎn)皮毛,而下面的這個(gè),可就要高級(jí)一些了。下面我們要實(shí)現(xiàn)的功能,在DOM的例子中已經(jīng)有實(shí)現(xiàn)了,就是從XML文檔中讀出內(nèi)容并格式化輸出,雖然程序邏輯看起來(lái)還是很簡(jiǎn)單,但是SAX可不比DOM哦,看著吧。

          前面說(shuō)過(guò),當(dāng)遇到一個(gè)開(kāi)始標(biāo)簽的時(shí)候,在startElement()方法中,我們并不能夠得到這個(gè)標(biāo)簽在XML文檔中所處的位置。這在處理XML文檔的時(shí)候是個(gè)大麻煩,因?yàn)樵赬ML中標(biāo)簽的語(yǔ)義,有一部分是由其所處的位置所決定的。而且在一些需要驗(yàn)證文檔結(jié)構(gòu)的程序中,這更是一個(gè)問(wèn)題。當(dāng)然,沒(méi)有解決不了的問(wèn)題了,我們可以使用一個(gè)棧來(lái)實(shí)現(xiàn)對(duì)文檔結(jié)構(gòu)的紀(jì)錄。

          棧的特點(diǎn)是先進(jìn)先出,我們現(xiàn)在的想法是,在startElemnt()方法中用push將這個(gè)標(biāo)簽的名字添加到棧中,在endElement()方法中在把它pop出來(lái)。我們知道對(duì)一個(gè)結(jié)構(gòu)良好的XML而言,其嵌套結(jié)構(gòu)是完備的,每一個(gè)開(kāi)始標(biāo)簽總會(huì)對(duì)應(yīng)一個(gè)結(jié)束標(biāo)簽,而且不會(huì)出現(xiàn)標(biāo)簽嵌套之間的錯(cuò)位。因而,每一次startElement()方法的調(diào)用,必然會(huì)對(duì)應(yīng)一個(gè)endElement()方法的調(diào)用,這樣push和pop也是成對(duì)出現(xiàn)的,我們只需要分析棧的結(jié)構(gòu),就可以很容易的知道當(dāng)前標(biāo)簽所處在文檔結(jié)構(gòu)中的位置了。

          public class SAXReader extends DefaultHandler {
          java.util.Stack tags=new java.util.Stack();
          //--------------XML Content-------------
          String text=null;
          String url=null;
          String author=null;
          String description=null;
          String day=null;
          String year=null;
          String month=null;
          //----------------------------------------------
          public void endDocument() throws SAXException {
          System.out.println("------Parse End--------");
          }
          public void startDocument() throws SAXException {
          System.out.println("------Parse Begin--------");
          }
          public void startElement(String p0, String p1, String p2, Attributes p3) throws SAXException {
          tags.push(p1);
          }
          public void endElement(String p0, String p1, String p2) throws SAXException {
          tags.pop();
          //一個(gè)link節(jié)點(diǎn)的信息收集齊了,將其格式化輸出
          if (p1.equals("link")) printout();
          }
          public void characters(char[] p0, int p1, int p2) throws SAXException {
          //從棧中得到當(dāng)前節(jié)點(diǎn)的信息
          String tag=(String) tags.peek();
          if (tag.equals("text")) text=new String(p0,p1,p2);
          else if (tag.equals("url")) url=new String(p0,p1,p2);
          else if (tag.equals("author")) author=new String(p0,p1,p2);
          else if (tag.equals("day")) day=new String(p0,p1,p2);
          else if (tag.equals("month")) month=new String(p0,p1,p2);
          else if (tag.equals("year")) year=new String(p0,p1,p2);
          else if (tag.equals("description")) year=new String(p0,p1,p2);
          }
          private void printout(){
          System.out.print("Content: ");
          System.out.println(text);
          System.out.print("URL: ");
          System.out.println(url);
          System.out.print("Author: ");
          System.out.println(author);
          System.out.print("Date: ");
          System.out.println(day+"-"+month+"-"+year);
          System.out.print("Description: ");
          System.out.println(description);
          System.out.println();
          }
          static public void main(String[] args) {
          String filename = null;
          boolean validation = false;
          filename="links.xml";
          SAXParserFactory spf = SAXParserFactory.newInstance();
          SAXParser saxParser=null;
          try {
          saxParser = spf.newSAXParser();
          } catch (Exception ex) {
          System.err.println(ex);
          System.exit(1);
          }
          try {
          saxParser.parse(new File(filename),new SAXReader());
          } catch (SAXException se) {
          System.err.println(se.getMessage());
          System.exit(1);
          } catch (IOException ioe) {
          System.err.println(ioe);
          System.exit(1);
          }
          }
          }

          在這兒雖然沒(méi)有使用到棧的分析,但實(shí)際上棧的分析是一件很容易的事情,應(yīng)為java.util.Stack繼承了java.util.Vector類,而且Stack中的元素是按棧的結(jié)構(gòu)由底至上排列的,因個(gè),我們可以使用Vector類的size()方法來(lái)得到Stack的元素個(gè)數(shù),還可以使用Vector的get(int)方法來(lái)得到具體的每一個(gè)元屬。實(shí)際上,如果把Stack的元素從底向上逐一排列出來(lái),我們就得到了從XML根節(jié)點(diǎn)到當(dāng)前節(jié)點(diǎn)的一條唯一的路徑,有了這條路徑的信息,文檔的結(jié)構(gòu)就在清楚不過(guò)了。

          小節(jié)
          好了,到這兒為止,我們已經(jīng)掌握了對(duì)于XML編程的兩大利器:DOM和SAX,也知道了該如何在一個(gè)Java程序中使用它們。DOM編程相對(duì)簡(jiǎn)單,但是速度比較慢,占用內(nèi)存多,而SAX編程復(fù)雜一些,但是速度快,占用內(nèi)存少。所以,我們應(yīng)該根據(jù)不同的環(huán)境選擇使用不同的方法。大部分的XML應(yīng)用基本都可以用它們來(lái)解決。需要特別說(shuō)明的是,DOM和SAX其實(shí)都是語(yǔ)言無(wú)關(guān)的,并非Java所獨(dú)有,也就是說(shuō),只要有相應(yīng)的語(yǔ)言實(shí)現(xiàn),DOM和SAX可以應(yīng)用在任何面向?qū)ο蟮恼Z(yǔ)言中。

          上面我們介紹了XML文檔的讀入,內(nèi)容提取,以及文檔添加和修改的一些方法。還有一類的問(wèn)題就是XML文檔的轉(zhuǎn)換,雖然用DOM和SAX也可以解決,但是實(shí)現(xiàn)起來(lái)很復(fù)雜,而應(yīng)用XSLT就會(huì)簡(jiǎn)單許多。這個(gè)問(wèn)題,筆者將會(huì)在以后的文章中再和大家詳細(xì)討論。

          源代碼下載
          (責(zé)任編輯 吳北 jiaoxq@staff.ccidnet.com
          posted on 2005-06-20 11:12 笨笨 閱讀(203) 評(píng)論(0)  編輯  收藏 所屬分類: J2EEALL
          主站蜘蛛池模板: 江山市| 揭阳市| 同仁县| 阜新市| 大石桥市| 高雄市| 武平县| 淳化县| 赤峰市| 西丰县| 东至县| 嵊州市| 肇东市| 永吉县| 左权县| 余江县| 临城县| 亚东县| 江川县| 易门县| 崇义县| 临漳县| 罗定市| 吉安县| 行唐县| 合川市| 嵊泗县| 疏勒县| 商城县| 铜川市| 抚远县| 衡阳县| 屏东县| 克什克腾旗| 阳谷县| 石门县| 揭西县| 鹤山市| 随州市| 南宁市| 古浪县|