在項目裏,咱們每每會把一些配置信息放到xml文件裏,或者各部門間會經過xml文件來交換業務數據,因此有時候咱們會遇到「解析xml文件」的需求。通常來說,有基於DOM樹和SAX的兩種解析xml文件的方式,在這部分裏,將分別給你們演示經過這兩種方式解析xml文件的通常步驟。java
1 XML的文件格式node
XML是可擴展標記語言(Extensible Markup Language)的縮寫,在其中,開始標籤和結束標籤必須配套地出現,咱們來看下book.xml這個例子。 編程
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 <books> 3 <book id="01"> 4 <name>Java</name> 5 <price>15</price> 6 <memo>good book</memo> 7 </book> 8 <book id="02"> 9 <name>FrameWork</name> 10 <price>20</price> 11 <memo>new book</memo> 12 </book> 13 </books>
整個xml文件是一個文檔(document),其中第1行表示文件頭,在第2和第13行裏,咱們能看到配套出現的books標籤,從標籤頭到標籤尾的部分那咱們稱之爲元素(element)。dom
因此咱們能夠這樣說,在books元素裏,咱們分別於第3到第7行和第8到第12行定義了2個book元素,在每一個book元素,好比從第4到第6行,又包含着3個元素,好比第一本書的name元素是<name>Java</name>,它的name元素值是Java。函數
在第3行裏,咱們還能看到元素裏的屬性(attribute),好比這個book元素具備id這個屬性,具體id的屬性值是01。性能
DOM是Document Object Model(文檔對象模型)的縮寫,在基於DOM樹的解析方式裏,解析代碼會先把xml文檔讀到內存裏,並整理成DOM樹的形式,隨後再讀取。根據以前部分裏給出的book.xml文檔,咱們能夠繪製出以下形式的DOM樹。ui
其中,books屬於根(root)結點,也叫根元素,因爲它包含着兩個book元素,因此第二層是兩個book結點,每一個book元素包含着3個元素,因此第三層是6個元素。在下面的ParserXmlByDom.java的代碼裏,咱們來看下經過DOM樹方式解析book.xml文檔的詳細步驟。this
1 //省略import相關類庫的代碼 2 public class ParserXmlByDom { 3 public static void main(String[] args) { 4 //建立DOM工廠 5 DocumentBuilderFactory domFactory=DocumentBuilderFactory.newInstance(); 6 InputStream input = null; 7 try { 8 //經過DOM工廠得到DOM解析器 9 DocumentBuilder domBuilder=domFactory.newDocumentBuilder(); 10 //把XML文檔轉化爲輸入流 11 input=new FileInputStream("src/book.xml"); 12 //解析XML文檔的輸入流,獲得一個Document 13 Document doc=domBuilder.parse(input);
從第5行到第13行,咱們完成了用DOM樹解析XML文件的準備工做,具體包括,在第5行裏建立了DOM工廠,在第9行經過DOM工廠建立了解析xml文件DocumentBuilder類型對象,在第11行把待解析的xml文件放入到一個InputStream類型的對象裏,在第13行經過parse方法把xml文檔解析成一個基於DOM樹結構的Document類型對象。 spa
14 //獲得XML文檔的根節點,只有根節點是Element類型 15 Element root=doc.getDocumentElement(); 16 // 獲得子節點 17 NodeList books = root.getChildNodes();
整個XML文件包含在第13行定義的doc對象裏,在第15行裏,咱們經過getDocumentElement方法獲得了根節點(也就是books節點),在第17行,經過getChildNoes方法獲得該books節點下的全部子節點,隨後開始解析整個xml文檔。xml
須要說明的是,在解析前,咱們會經過觀察xml文檔來了解其中的元素名和屬性名,因此在後繼的代碼裏,咱們會針對元素名和屬性名進行編程。
18 if(books!=null){ 19 for(int i=0;i<books.getLength();i++){ 20 Node book=books.item(i); 21 //獲取id屬性 22 if(book.getNodeType()==Node.ELEMENT_NODE){ 23 String id=book.getAttributes().getNamedItem("id").getNodeValue(); 24 System.out.println("id is:" + id); 25 //遍歷book下的子節點 26 for(Node node=book.getFirstChild(); node!=null;node=node.getNextSibling()){ 27 if(node.getNodeType()==Node.ELEMENT_NODE){ 28 //依次讀取book裏的name,price和memo三個子元素 29 if(node.getNodeName().equals("name")){ 30 String name=node.getFirstChild().getNodeValue(); 31 System.out.println("name is:" + name); 32 } 33 if(node.getNodeName().equals("price")){ 34 String price=node.getFirstChild().getNodeValue(); 35 System.out.println("price is:" + price); 36 } 37 if(node.getNodeName().equals("memo")){ 38 String memo=node.getFirstChild().getNodeValue(); 39 System.out.println("memo is:" + memo); 40 } 41 } 42 } 43 } 44 } 45 }
第19行的for循環裏,咱們是遍歷book元素經過觀察xml文件,咱們發現book元素出現了2次,全部這個循環會運行兩次,並且,book元素有1個id屬性,全部咱們須要經過第23行的代碼,獲得id屬性的值。
在文檔裏,book元素有3個子節點,分別是name,price和memo,因此在代碼的26行裏,再次使用for循環遍歷其中的子節點。在遍歷時,咱們經過29到32行的代碼獲取到了book元素裏name的值,經過相似的代碼後繼的33到40行代碼裏獲得了price和memo這兩個元素的值。
46 } catch (ParserConfigurationException e) { 47 e.printStackTrace(); 48 } catch (FileNotFoundException e) { 49 e.printStackTrace(); 50 } catch (IOException e) { 51 e.printStackTrace(); 52 } catch (SAXException e) { 53 e.printStackTrace(); 54 } catch (Exception e) { 55 e.printStackTrace(); 56 } 57 //在finally裏關閉io流 58 finally{ 59 try { 60 input.close(); 61 } catch (IOException e) { 62 e.printStackTrace(); 63 } 64 } 65 } 66 }
一樣地,在解析完成後,在finally從句裏,咱們關閉了以前用到的IO流(input對象)。
SAX是Simple API for XML的縮寫,不一樣於DOM的文檔驅動,它是事件驅動的,也就是說,它是一種基於回調(callback)函數的解析方式,好比開始解析xml文檔時,會調用咱們本身定義的startDocument函數,從下表裏,咱們能看到基於SAX方式裏的各類回調函數以及它們被調用的時間點。
函數名 |
調用時間點 |
startDocument |
開始解析xml文檔時(解析xml文檔第一個字符時)會被調用 |
endDocument |
當解析完xml文檔時(解析到xml文檔最後一個字符時)會被調用 |
startElement |
當解析到開始標籤時會被調用,好比在解析「<name>FrameWork</name>」這個element時,當讀到開始標籤「<name>」時,會被調用 |
endElement |
當解析到結束標籤時會被調用,好比在解析「<name>FrameWork</name>」這個element時,當讀到結束標籤「</name>」時,會被調用 |
characters |
1行開始後,遇到開始或結束標籤以前存在字符,則會調用 2兩個標籤之間,存在字符,則會調用,好比在解析「<name>FrameWork</name>」時,發現存在FrameWork,則會被調用 3標籤和行結束符以前存在字符,則會調用 |
從上表裏咱們能看到characters方法會在多個場合被回調,但咱們最指望的調用場景是第2種,這就要求咱們最好在解析xml文檔前整理下它的格式,儘可能避免第1和第3種狀況。在ParserXmlBySAX.java這個案例中,咱們經過了編寫上述的回調函數,實現了SAX方式解析xml文檔的功能。
1 //省略import的代碼 2 //基於SAX的解析代碼須要繼承DefaultHandler類 3 public class ParserXmlBySAX extends DefaultHandler{ 4 // 記錄當前解析到的節點名 5 private String tagName; 6 //主方法 7 public static void main(String[] argv) { 8 String uri = "src/book.xml"; 9 try { 10 SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 11 ParserXmlBySAX myParser = new ParserXmlBySAX(); 12 SAXParser parser = parserFactory.newSAXParser(); 13 parser.parse(uri, myParser); 14 } catch (IOException ex) { 15 ex.printStackTrace(); 16 } catch (SAXException ex) { 17 ex.printStackTrace(); 18 } catch (ParserConfigurationException ex) { 19 ex.printStackTrace(); 20 } catch (FactoryConfigurationError ex) { 21 ex.printStackTrace(); 22 } 23 }
在main方法的第8行裏,咱們指定了待解析xml文檔的路徑和文件名,在第10行裏,咱們建立了SAXParserFactory這個類型的SAX解析工廠對象。在第12行,咱們經過SAX解析工廠對象,建立了SAXParser這個類型的解析類。在第13行,經過了parse方法啓動了解析。
在上文裏咱們就已經知道,在SAX的方式裏,是經過調用各類回調函數來完成解析的,因此在代碼裏,咱們還得自定義各個回調函數,代碼以下。
// 處理到文檔結尾時,直接輸出,不作任何動做 25 public void endDocument() throws SAXException { 26 System.out.println("endDocument"); 27 } 28 // 處理到結束標籤時,把記錄當前標籤名的tagName設置成null 29 public void endElement(String uri, String localName, String qName) throws SAXException { 30 tagName = null; 31 } 32 // 開始處理文檔時,直接輸出,不作任何動做 33 public void startDocument() throws SAXException { 34 System.out.println("startDocument"); 35 } 36 // 處理開始標籤 37 public void startElement(String uri, String localName, String name,Attributes attributes) throws SAXException { 38 if ("book".equals(name)) { //解析book標籤的屬性 39 for (int i = 0; i < attributes.getLength(); i++) { 40 System.out.println("attribute name is:" + attributes.getLocalName(i) + " attribute value:" + attributes.getValue(i)); 41 } 42 } 43 //把當前標籤的名字記錄到tagName這個變量裏 44 tagName = name; 45 } 46 //經過這個方法解析book的三個子元素的值 47 public void characters(char[] ch, int start, int length) 48 throws SAXException { 49 if(this.tagName!=null){ 50 String val=new String(ch,start,length); 51 //若是是name,price或memo,則輸出它們的值 52 if("name".equals(tagName)) 53 { System.out.println("name is:" + val); } 54 if("price".equals(tagName)) 55 { System.out.println("price is:" + val); } 56 if("memo".equals(tagName)) 57 { System.out.println("memo is:" + val); } 58 } 59 } 60 }
咱們用tagName來保存當前的標籤名,是爲了解析book元素的name,price和memo這三個子元素。
<name>FrameWork</name>
好比當解析到name這個開始標籤時,在第44行裏,startElement會把tagname值設置成name,當解析到FramWork時,因爲它包含在兩個標籤之間,因此會被觸發第47行的characters方法,在其中的第52行的if判斷裏,因爲得知當前的標籤名是name,因此會輸出FrameWork這個name元素的值,當解析到</name>這個結束標籤時,會觸發第29行的endElement方法,在其中的30行裏,會把tagName值清空。
這段代碼的輸出結果以下,其中第1行和第10行分別是在開始解析和完成解析時輸出的。
第2行鍼對id屬性的輸出是在startElement方法的第40行裏被打印的,第3到第5行鍼對3個book子元素的輸出是在characters方法裏被打印的。
第2到第5行是針對第一個book元素的輸出,而第6到第9行是針對第2個book。
1 startDocument 2 attribute name is:id attribute value:01 3 name is:Java 4 price is:15 5 memo is:good book 6 attribute name is:id attribute value:02 7 name is:FrameWork 8 price is:20 9 memo is:new book 10 endDocument
在基於DOM的方式裏,因爲咱們會把整個xml文檔以DOM樹的方式裝載到內存裏,因此能夠邊解析邊修改,並且還能再次解析已經被解析過的內容。
而在SAX的方式裏,因爲咱們是以基於回調函數的方式來解析,因此並不須要把整個文檔載入到內存,這樣能節省內存資源。
因此說,選擇 DOM 仍是 SAX,這取決於以下三個個因素。
第一,若是咱們在解析時還打算更新xml裏的數據,那麼建議使用DOM方式。
第二,若是待解析的文件過大,把它所有裝載到內存時可能會影響到內存性能,那麼建議使用SAX的方式。
第三,若是咱們對解析的速度有必定的要求,那麼建議使用SAX方式,由於它比DOM方式要快些。