天天看點

Java解析xml的主要解析器: SAX和DOM的選擇

Java的xml解析器庫有很多,總的來說,萬變不離其宗的就是SAX和DOM解析器。 SAX的包是org.xml.sax DOM的包是org.w3c.dom   1) DOM      DOM 是用與平台和語言無關的方式表示 XML 文檔的官方 W3C 标準。DOM 是以層次結構組織的節點或資訊片斷的集合。這個層次結構允許開發人員在樹中尋找特定資訊。分析該結構通常需要加載整個文檔和構造層次結構,然後才能做任何工作。由于它是基于資訊層次的,因而 DOM 被認為是基于樹或基于對象的。DOM 以及廣義的基于樹的處理具有幾個優點。首先,由于樹在記憶體中是持久的,是以可以修改它以便應用程式能對資料和結構作出更改。它還可以在任何時候在樹中上下導航,而不是像 SAX 那樣是一次性的處理。DOM 使用起來也要簡單得多。       另一方面,對于特别大的文檔,解析和加載整個文檔可能很慢且很耗資源,是以使用其他手段來處理這樣的資料會更好。這些基于事件的模型,比如 SAX。    2) SAX       這種處理的優點非常類似于流媒體的優點。分析能夠立即開始,而不是等待所有的資料被處理。而且,由于應用程式隻是在讀取資料時檢查資料,是以不需要将資料存儲在記憶體中。這對于大型文檔來說是個巨大的優點。事實上,應用程式甚至不必解析整個文檔;它可以在某個條件得到滿足時停止解析。一般來說,SAX 還比它的替代者 DOM 快許多。    3) 選擇 DOM 還是選擇 SAX ?       對于需要自己編寫代碼來處理 XML 文檔的開發人員來說,選擇 DOM 還是 SAX 解析模型是一個非常重要的設計決策。      DOM 采用建立樹形結構的方式通路 XML 文檔,而 SAX 采用的事件模型。       DOM 解析器把 XML 文檔轉化為一個包含其内容的樹,并可以對樹進行周遊。用 DOM 解析模型的優點是程式設計容易,開發人員隻需要調用建樹的指令,然後利用navigation APIs通路所需的樹節點來完成任務。可以很容易的添加和修改樹中的元素。然而由于使用 DOM 解析器的時候需要處理整個 XML 文檔,是以對性能和記憶體的要求比較高,尤其是遇到很大的 XML 檔案的時候。由于它的周遊能力,DOM 解析器常用于 XML 文檔需要頻繁的改變的服務中。    SAX 解析器采用了基于事件的模型,它在解析 XML 文檔的時候可以觸發一系列的事件,當發現給定的tag的時候,它可以激活一個回調方法,告訴該方法制定的标簽已經找到。SAX 對記憶體的要求通常會比較低,因為它讓開發人員自己來決定所要處理的tag。特别是當開發人員隻需要處理文檔中所包含的部分資料時,SAX 這種擴充能力得到了更好的展現。但用 SAX 解析器的時候編碼工作會比較困難,而且很難同時通路同一個文檔中的多處不同資料。   個人總結 ******************* DOM:        解析器讀入整個文檔,然後建構一個駐留記憶體的樹結構,然後代碼就可以使用 DOM 接口來操作這個樹結構。        優點:整個文檔樹在記憶體中,便于操作;支援删除、修改、重新排列等多種功能;        缺點:将整個文檔調入記憶體(包括無用的節點),浪費時間和空間;        使用場合:一旦解析了文檔還需多次通路這些資料;硬體資源充足(記憶體、CPU)   SAX:        事件驅動。當解析器發現元素開始、元素結束、文本、文檔的開始或結束等時,發送事件,程式員編寫響應這些事件的代碼,儲存資料。        優點:不用事先調入整個文檔,占用資源少        缺點:不是持久的;事件過後,若沒儲存資料,那麼資料就丢了;無狀态性;從事件中隻能得到文本,但不知該文本屬于哪個元素;        使用場合:隻需XML文檔的少量内容,很少回頭通路;一次性讀取;機器記憶體少;        注意:SAX 解析器不建立任何對象。   *********************** DOM和SAX的使用例子 ( http://sinye.javaeye.com/blog/763926 /  http://www.javaeye.com/topic/763895) Xml檔案内容

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <books> 
  3.     <book id="12"> 
  4.         <name>thinking in java</name> 
  5.         <price>85.5</price> 
  6.     </book> 
  7.     <book id="15"> 
  8.         <name>Spring in Action</name> 
  9.         <price>39.0</price> 
  10.     </book> 
  11. </books> 

Book.java如下:主要是用來組裝資料

  1. public class Book { 
  2.     private int id; 
  3.     private String name; 
  4.     private float price; 
  5.     public int getId() { 
  6.         return id; 
  7.     } 
  8.     public void setId(int id) { 
  9.         this.id = id; 
  10.     } 
  11.     public String getName() { 
  12.         return name; 
  13.     } 
  14.     public void setName(String name) { 
  15.         this.name = name; 
  16.     } 
  17.     public float getPrice() { 
  18.         return price; 
  19.     } 
  20.     public void setPrice(float price) { 
  21.         this.price = price; 
  22.     } 
  23.     @Override 
  24.     public String toString(){ 
  25.         return this.id+":"+this.name+":"+this.price; 
  26.     } 

1. Dom解析是将xml檔案全部載入,組裝成一顆dom樹,然後通過節點以及節點之間的關系來解析xml檔案,結合一張圖來發現dom解析時需要注意的地方

Java解析xml的主要解析器: SAX和DOM的選擇

在這裡當我們得到節點book時,也就是圖中1所畫的地方,如果我們調用它的getChildNodes()方法,大家猜猜它的子節點有幾個?不包括它的孫子節點,thinking in java這種的除外,因為它是孫子節點。它總共有5個子節點,分别是圖中2、3、4、5、6所示的那樣。是以在解析時,一定要小心,不要忽略空白的地方。 然後看代碼來解析book.xml檔案 DomParseService.java

  1. import java.io.InputStream; 
  2. import java.util.ArrayList; 
  3. import java.util.List; 
  4. import javax.xml.parsers.DocumentBuilder; 
  5. import javax.xml.parsers.DocumentBuilderFactory; 
  6. import org.w3c.dom.Document; 
  7. import org.w3c.dom.Element; 
  8. import org.w3c.dom.NodeList; 
  9. import org.w3c.dom.Node; 
  10. import com.xtlh.cn.entity.Book; 
  11. public class DomParseService { 
  12.     public List<Book> getBooks(InputStream inputStream) throws Exception{ 
  13.         List<Book> list = new ArrayList<Book>(); 
  14.         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
  15.         DocumentBuilder builder = factory.newDocumentBuilder(); 
  16.         Document document = builder.parse(inputStream); 
  17.         Element element = document.getDocumentElement(); 
  18.         NodeList bookNodes = element.getElementsByTagName("book"); 
  19.         for(int i=0;i<bookNodes.getLength();i++){ 
  20.             Element bookElement = (Element) bookNodes.item(i); 
  21.             Book book = new Book(); 
  22.             book.setId(Integer.parseInt(bookElement.getAttribute("id"))); 
  23.             NodeList childNodes = bookElement.getChildNodes(); 
  24. //          System.out.println("*****"+childNodes.getLength()); 
  25.             for(int j=0;j<childNodes.getLength();j++){ 
  26.                 if(childNodes.item(j).getNodeType()==Node.ELEMENT_NODE){ 
  27.                     if("name".equals(childNodes.item(j).getNodeName())){ 
  28.                         book.setName(childNodes.item(j).getFirstChild().getNodeValue()); 
  29.                     }else if("price".equals(childNodes.item(j).getNodeName())){ 
  30.                         book.setPrice(Float.parseFloat(childNodes.item(j).getFirstChild().getNodeValue())); 
  31.                     } 
  32.                 } 
  33.             }//end for j 
  34.             list.add(book); 
  35.         }//end for i 
  36.         return list; 
  37.     } 

測試使用單元測試如下ParseTest.java

  1. public class ParseTest extends TestCase{ 
  2.     public void testDom() throws Exception{ 
  3.         InputStream input = this.getClass().getClassLoader().getResourceAsStream("book.xml"); 
  4.         DomParseService dom = new DomParseService(); 
  5.         List<Book> books = dom.getBooks(input); 
  6.         for(Book book : books){ 
  7.             System.out.println(book.toString()); 
  8.         } 
  9.     } 

  2. Sax解析是按照xml檔案的順序一步一步的來解析,在解析xml檔案之前,我們要先了解xml檔案的節點的種類,一種是ElementNode,一種是TextNode。     其中,像<books>、<book>這種節點就屬于ElementNode,而thinking in java、85.5這種就屬于TextNode。 下面結合一張圖來詳細講解Sax解析。

Java解析xml的主要解析器: SAX和DOM的選擇

    xml檔案被Sax解析器載入,由于Sax解析是按照xml檔案的順序來解析,當讀入<?xml.....>時,會調用startDocument()方法,當讀入<books>的時候,由于它是個ElementNode,是以會調用startElement(String uri, String localName, String qName, Attributes attributes) 方法,其中第二個參數就是節點的名稱,注意:由于有些環境不一樣,有時候第二個參數有可能為空,是以可以使用第三個參數,是以在解析前,先調用一下看哪個參數能用,第4個參數是這個節點的屬性。這裡我們不需要這個節點,是以從<book>這個節點開始,也就是圖中1的位置,當讀入時,調用startElement(....)方法,由于隻有一個屬性id,可以通過attributes.getValue(0)來得到,然後在圖中标明2的地方會調用characters(char[] ch, int start, int length)方法,不要以為那裡是空白,Sax解析器可不那麼認為,Sax解析器會把它認為是一個TextNode。但是這個空白不是我們想要的資料,我們是想要<name>節點下的文本資訊。這就要定義一個記錄當上一節點的名稱的TAG,在characters(.....)方法中,判斷目前節點是不是name,是再取值,才能取到thinking in java。具體見代碼:SaxParseService.java

  1. import java.io.InputStream; 
  2. import java.util.ArrayList; 
  3. import java.util.List; 
  4. import javax.xml.parsers.SAXParser; 
  5. import javax.xml.parsers.SAXParserFactory; 
  6. import org.xml.sax.Attributes; 
  7. import org.xml.sax.SAXException; 
  8. import org.xml.sax.helpers.DefaultHandler; 
  9. import com.xtlh.cn.entity.Book; 
  10. public class SaxParseService extends DefaultHandler{ 
  11.     private List<Book> books = null; 
  12.     private Book book = null; 
  13.     private String preTag = null;//作用是記錄解析時的上一個節點名稱 
  14.     public List<Book> getBooks(InputStream xmlStream) throws Exception{ 
  15.         SAXParserFactory factory = SAXParserFactory.newInstance(); 
  16.         SAXParser parser = factory.newSAXParser(); 
  17.         SaxParseService handler = new SaxParseService(); 
  18.         parser.parse(xmlStream, handler); 
  19.         return handler.getBooks(); 
  20.     } 
  21.     public List<Book> getBooks(){ 
  22.         return books; 
  23.     } 
  24.     @Override 
  25.     public void startDocument() throws SAXException { 
  26.         books = new ArrayList<Book>(); 
  27.     } 
  28.     @Override 
  29.     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 
  30.         if("book".equals(qName)){ 
  31.             book = new Book(); 
  32.             book.setId(Integer.parseInt(attributes.getValue(0))); 
  33.         } 
  34.         preTag = qName;//将正在解析的節點名稱賦給preTag 
  35.     } 
  36.     @Override 
  37.     public void endElement(String uri, String localName, String qName) 
  38.             throws SAXException { 
  39.         if("book".equals(qName)){ 
  40.             books.add(book); 
  41.             book = null; 
  42.         } 
  43.         preTag = null;/**當解析結束時置為空。這裡很重要,例如,當圖中畫3的位置結束後,會調用這個方法 
  44.         ,如果這裡不把preTag置為null,根據startElement(....)方法,preTag的值還是book,當文檔順序讀到圖 
  45.         中标記4的位置時,會執行characters(char[] ch, int start, int length)這個方法,而characters(....)方 
  46.         法判斷preTag!=null,會執行if判斷的代碼,這樣就會把空值指派給book,這不是我們想要的。*/ 
  47.     } 
  48.     @Override 
  49.     public void characters(char[] ch, int start, int length) throws SAXException { 
  50.         if(preTag!=null){ 
  51.             String content = new String(ch,start,length); 
  52.             if("name".equals(preTag)){ 
  53.                 book.setName(content); 
  54.             }else if("price".equals(preTag)){ 
  55.                 book.setPrice(Float.parseFloat(content)); 
  56.             } 
  57.         } 
  58.     } 

測試是用的單元測試,測試代碼如下:ParseTest

  1. import java.io.InputStream; 
  2. import java.util.List; 
  3. import junit.framework.TestCase; 
  4. import com.xtlh.cn.demo.DomParseService; 
  5. import com.xtlh.cn.demo.SaxParseService; 
  6. import com.xtlh.cn.entity.Book; 
  7. public class ParseTest extends TestCase{ 
  8.     public void testSAX() throws Throwable{ 
  9.         SaxParseService sax = new SaxParseService(); 
  10.         InputStream input = this.getClass().getClassLoader().getResourceAsStream("book.xml"); 
  11.         List<Book> books = sax.getBooks(input); 
  12.         for(Book book : books){ 
  13.             System.out.println(book.toString()); 
  14.         } 
  15.     } 

在用Sax解析的時候最需要重視的一點就是不要把那些<節點>之間的空白忽略就好!           最近了解到JDK6 添加一個名為StAX的新解析方法,具體可以參考 在JDK 6.0中基于StAX分析XML資料。在J2ME下可以使用XmlPullParser,參考 http://www.javaeye.com/topic/41564。這些解析方法都是pull parser。按照文章裡的網友說法,“pull parser為什麼快?sax parser為什麼慢?我覺得這是關鍵。我曾經在delphi上把一個sax parser改裝成pull parser。其實很簡單,隻要把回調的API改成基于循環的主動查詢。做的是減法”!!看完了下面的例子 http://www.javaeye.com/topic/763949,似乎明白了一點。      這個例子所使用的xml檔案和Java實體類和上面兩個例子一樣的。       Pull解析和Sax解析很相似,都是輕量級的解析,在Android的核心中已經嵌入了Pull,是以我們不需要再添加第三方jar包來支援Pull。Pull解析和Sax解析不一樣的地方有(1)pull讀取xml檔案後觸發相應的事件調用方法傳回的是數字(2)pull可以在程式中控制想解析到哪裡就可以停止解析。 Pull解析的代碼如下PullParseService.java

  1. import java.io.InputStream; 
  2. import java.util.ArrayList; 
  3. import java.util.List; 
  4. import org.xmlpull.v1.XmlPullParser; 
  5. import android.util.Xml; 
  6. import com.xtlh.cn.entity.Book; 
  7. public class PullParseService { 
  8.     public static List<Book> getBooks(InputStream inputStream) throws Exception{ 
  9.         List<Book> books = null; 
  10.         Book book = null; 
  11.         XmlPullParser parser = Xml.newPullParser(); 
  12.         parser.setInput(inputStream, "UTF-8"); 
  13.         int event = parser.getEventType();//産生第一個事件 
  14.         while(event!=XmlPullParser.END_DOCUMENT){ 
  15.             switch(event){ 
  16.             case XmlPullParser.START_DOCUMENT://判斷目前事件是否是文檔開始事件 
  17.                 books = new ArrayList<Book>();//初始化books集合 
  18.                 break; 
  19.             case XmlPullParser.START_TAG://判斷目前事件是否是标簽元素開始事件 
  20.                 if("book".equals(parser.getName())){//判斷開始标簽元素是否是book 
  21.                     book = new Book(); 
  22.                     book.setId(Integer.parseInt(parser.getAttributeValue(0)));//得到book标簽的屬性值,并設定book的id 
  23.                 } 
  24.                 if(book!=null){ 
  25.                     if("name".equals(parser.getName())){//判斷開始标簽元素是否是name 
  26.                         book.setName(parser.nextText()); 
  27.                     }else if("price".equals(parser.getName())){//判斷開始标簽元素是否是price 
  28.                         book.setPrice(Float.parseFloat(parser.nextText())); 
  29.                     } 
  30.                 } 
  31.                 break; 
  32.             case XmlPullParser.END_TAG://判斷目前事件是否是标簽元素結束事件 
  33.                 if("book".equals(parser.getName())){//判斷結束标簽元素是否是book 
  34.                     books.add(book);//将book添加到books集合 
  35.                     book = null; 
  36.                 } 
  37.                 break; 
  38.             } 
  39.             event = parser.next();//進入下一個元素并觸發相應事件 
  40.         }//end while 
  41.         return books; 
  42.     } 

測試使用的是android的單元測試,代碼如下:

  1. import java.io.InputStream; 
  2. import java.util.List; 
  3. import android.test.AndroidTestCase; 
  4. import android.util.Log; 
  5. import com.xtlh.cn.entity.Book; 
  6. import com.xtlh.cn.service.PullParseService; 
  7. public class testPullParseService extends AndroidTestCase{ 
  8.     private static final String TAG = "testPullParseService"; 
  9.     public void testPull() throws Exception{ 
  10.         InputStream input = this.getClass().getClassLoader().getResourceAsStream("book.xml"); 
  11.         PullParseService pull = new PullParseService(); 
  12.         List<Book> books = pull.getBooks(input); 
  13.         for(Book book : books){ 
  14.             Log.i(TAG,book.toString()); 
  15.         } 
  16.     }