Java解析XML文件的方式


    在項目里,我們往往會把一些配置信息放到xml文件里,或者各部門間會通過xml文件來交換業務數據,所以有時候我們會遇到“解析xml文件”的需求。一般來講,有基於DOM樹和SAX的兩種解析xml文件的方式,在這部分里,將分別給大家演示通過這兩種方式解析xml文件的一般步驟。

1 XML的文件格式

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

    所以我們可以這樣說,在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樹。

    

 

     其中,books屬於根(root)結點,也叫根元素,由於它包含着兩個book元素,所以第二層是兩個book結點,每個book元素包含着3個元素,所以第三層是6個元素。在下面的ParserXmlByDom.java的代碼里,我們來看下通過DOM樹方式解析book.xml文檔的詳細步驟。

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類型對象。    

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文檔來了解其中的元素名和屬性名,所以在后繼的代碼里,我們會針對元素名和屬性名進行編程。    

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方式要快些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM