Java解析XML文件的方式

    在項目裏,咱們每每會把一些配置信息放到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。性能

2 基於DOM樹的解析方式

    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對象)。

3 基於事件的解析方式

    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

  

4 DOM和SAX兩種解析方式的應用場景

    在基於DOM的方式裏,因爲咱們會把整個xml文檔以DOM樹的方式裝載到內存裏,因此能夠邊解析邊修改,並且還能再次解析已經被解析過的內容。

    而在SAX的方式裏,因爲咱們是以基於回調函數的方式來解析,因此並不須要把整個文檔載入到內存,這樣能節省內存資源。

    因此說,選擇 DOM 仍是 SAX,這取決於以下三個個因素。

    第一,若是咱們在解析時還打算更新xml裏的數據,那麼建議使用DOM方式。

    第二,若是待解析的文件過大,把它所有裝載到內存時可能會影響到內存性能,那麼建議使用SAX的方式。

    第三,若是咱們對解析的速度有必定的要求,那麼建議使用SAX方式,由於它比DOM方式要快些。

相關文章
相關標籤/搜索