1. 程式人生 > >[Java]利用DOM解析DOM檔案|利用socket傳送XML DOM

[Java]利用DOM解析DOM檔案|利用socket傳送XML DOM

DOM的特點是隨機讀寫查詢,但是一次性要把xml讀到記憶體裡,對於大的xml檔案而言,不是個好方法。

加之我個人覺得W3C的Document這套體系的坑比較多,如果要用的話,需要非常系統的學習才能避開這些坑,挺麻煩的。

所以我還是建議採用SAX或其他已包裝好的API來寫xml比較好。

看了一圈,感覺寫得簡潔、系統性的部落格不多,大多嵌套了太多其他雜亂的相關知識。

>DOM下利用Java對XML進行解析(導包:javax.xml.parsers.*)

//1.獲取工廠類
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
//2.從工廠中得到一個解析器
DocumentBuilder db=dbf.newDocumentBuilder();
//3.利用解析器解析檔案,獲得document物件
Document doc=db.parse(new File(url));

利用parse方法獲取Document共有以下幾種方法:

利用第一個方法,可以由String構建一個DOM。關於InputSource(包:org.xml.sax.*)的問題,參見文末。

--------------------------

由於我們的目的只是為了獲取Document,所以上面的程式碼可以更加的精簡,把1、2步合成到一個地方去,所以整個步驟最後只有兩步:

DocumentBuilder db=DocumentBuilderFactory.newInstance().newDocumentBuilder();//獲取解析器
Document doc=db.parse(new File(url));//從檔案解析成document檔案

接下來,從這個document檔案物件中獲取每個位元組點的列表,並對單個節點的資料進行輸出(相當於解析獲取資料):
//4.得到所有根部節的列表
NodeList list=doc.getElementsByTagName("employee");//或 doc.getChildNodes();
//5.遍歷、解析輸出等操作
for(int i=0;i<list.getLength();i++){
	Node n=list.item(i);//假設這裡用的是Node進行操作
	if(n.getNodeType()==Node.ELEMENT_NODE){//坑1:判斷是否為有效文字字元
		System.out.println("\t"+n.getNodeName()+":"+n.getTextContent());//坑2:getNodeValue()方法輸出均為null
	}
}

注意到上面的程式碼我標註了兩個坑,待會兒我再來說。

-----------------------

BTW:

有人可能要問,如果用Element進行操作,是不是就能避開空白文字的問題呢?比如:

for(int j=0;j<nlist.getLength();j++){
	Element e=(Element)nlist.item(j);
        //輸出操作等,略
}
答案是不能。注意到我們上面是採用:
NodeList list=doc.getElementsByTagName("employee");//語句1

的方法獲取節點列表的,也就意味著,空白節點(空格啥的)也進來了。此處進行Node->Element的強制轉換很可能報錯型別轉換失敗,處理方法有兩種:

1.把語句1中的方法換成:getChildNodes()。

但這樣其實十分不靈活,沒法按標籤獲取。

2.新增判斷,也就是:

Node info=nlist.item(j);
if(info.getNodeType()==Node.ELEMENT_NODE){
	Element e=(Element) info;
}
但這樣看著非常多此一舉。

>利用socket傳送xml檔案

xml作為一種資料型別,肯定免不了在不同的伺服器間進行傳送。

但我個人建議還是採用json來進行這種資料交換(更輕量化、更快捷),或者至少,不要用DOM下的xml進行這種操作。

json的相關內容見此:

但如果你執意要用DOM進行這個操作,好吧,接著往下看:

首先,我們知道socket傳送的資料可以是字串,或者隨意什麼位元組流,那麼我們只要把xml編譯成一個string或是bytes[]就可以傳送了。

關於socket的相關知識看此:

至於轉化和再編成document檔案,程式碼見下:

//轉化為xml-string便於用socket傳送
Transformer tr=TransformerFactory.newInstance().newTransformer();//獲得一個轉化器
tr.setOutputProperty("encoding","UTF-8");//設定編碼格式
ByteArrayOutputStream  bos  =  new  ByteArrayOutputStream();
tr.transform(new DOMSource(doc), new StreamResult(bos));//bos就是要傳送的流
//再編碼
InputStream is= new ByteArrayInputStream(bos.toString().getBytes());
Document doc2=db.parse(is);//doc2就是接收方獲取到的資料再編碼成document的


這裡還有一個坑,是關於InputSource()方法的,稱其為坑3,留到下面再說。

至此,具體應用部分講完了。後續會考慮更新DOM下的xml“改刪加”操作,以及SAX的解析。

>神坑

(下面這部分是基礎知識的內容,如果只是偏向應用,請直接無視,等遇上問題了再來看)

其一:
n.getNodeType()==Node.ELEMENT_NODE 

寫這一行的目的是為了判斷這個節點是否是有效的標籤節點,也就是:<tag>value</tag>。

因為空白字元(空格換行製表符等)並不會被預設丟棄,而是被單做一個有效文字,所以如果不進行這一行的判定,你很可能System.out.plintln(...)出來的字串會是:#text

-----------

其二:
getNodeValue()方法輸出均為null

肯定不止我一個人注意到除了getTextContent方法,還有一個名字更為蠱惑的getNodeValue,要是一沒注意,鐵定就著道了。

為什麼getNodeValue輸出的都是null呢?

因為Dom解析器中Node和Element是一個內含的關係,Node指的是哪些呢?

答案是任何,一個空格,一個標籤,一個Element都可以是一個Node。

而Element指的是<tag>value</tag>這樣的東西。(可以認為Element屬於Node)

而Node的getNodeValue只有對是Element的Node的內容才有意義。

所以我其實非常無語這個方法的原始設定,感覺除非是什麼特殊用途,不然沒啥能用得上的地方,還容易造成歧義。

這裡可看進一步研究其差異:

-----------

其三:
DocumentBuilder.parse(InputSource is)方法無效

這個真的是神坑。明明有這個方法,但是無論怎麼輸入都是不對的:

StringReader sr = new StringReader(bos.toString());   
InputSource is=new InputSource(sr);
Document doc2=db.parse(is);//此處編譯不通過,如下圖:


原因是什麼呢?其實這是個抽象方法:


且這個InputSource是屬於包:org.xml.sax.InputSource,導錯包就會導致引數不匹配。