前面介紹了JSON格式的報文解析,雖然json串短小精悍,也能有效表達層次結構,可是每一個元素只能找到對應的元素值,不能體現更豐富的樣式特徵。好比某個元素除了要傳輸它的字符串文本,還想傳輸該文本的類型、字體大小、字體顏色等特徵,且這些額外的風格樣式與業務邏輯無關,天然不適合爲它們單獨設立參數字段。假若採用JSON格式定義包括樣式特徵在內的文本元素,要麼摒棄風格樣式這種附加屬性,要麼將風格樣式單列爲專門的字段參數,然而無論哪一種作法,都未能妥善解決附加屬性的表達問題。可見輕量級的JSON格式依然存在力不從心的狀況,爲此人們早早發明了擁有強大表示能力的XML格式,XML的全稱是「Extensible Markup Language」(可擴展標記語言),它不但支持結構化數據的描述,還支持各種附加屬性的定義,很是適合在網絡中傳輸信息。
下面先看一個XML報文格式的購物訂單樣例:html
<?xml version="1.0" encoding="gbk"?> <order> <user_info> <name type="string">思無邪</name> <address type="string">桃花島水簾洞123號</address> <phone type="string">15960238696</phone> </user_info> <goods_list> <goods_item> <goods_name type="string">Mate30</goods_name> <goods_number type="int">1</goods_number> <goods_price type="double">8888</goods_price> </goods_item> <goods_item> <goods_name type="string">格力中央空調</goods_name> <goods_number type="int">1</goods_number> <goods_price type="double">58000</goods_price> </goods_item> <goods_item> <goods_name type="string">紅蜻蜓皮鞋</goods_name> <goods_number type="int">3</goods_number> <goods_price type="double">999</goods_price> </goods_item> </goods_list> </order>
接着對上面的XML樣例庖丁解牛,分析一下XML格式都有哪些特色,分析結果羅列以下:
一、每一個元素依然由參數名稱和參數值組成,參數名稱爲尖括號所包裹,且分爲標記頭與標記尾兩部分,標記尾在尖括號內部多了個斜杆。如此一來,一個字段的完整形式爲「<參數名稱>參數值</參數名稱>」。
二、由於每一個元素都自帶標記頭與標記尾,很容易區分在哪開始在哪結束,因此元素之間無需額外的分隔符,只要有標記頭與標記尾就足夠辨別了。
三、每一個結構也須要專門的標記頭與標記尾,中間再填入若干元素或者其它結構。
四、對於數組形式的數據,XML報文采用多個同名的結構標記並排列舉,表示這裏存在同名結構的數組信息,也可看做是清單信息。
五、XML格式容許在報文開頭的encoding屬性處指定當前報文的字符編碼類型,常見的有漢字內碼規範GBK,以及世界通用編碼規範UTF-8。
六、每一個結構或者元素節點,也支持在標記頭部分填充附加屬性,用於指定參數值之外的特定信息。
大體瞭解了XML報文的格式規範,還得在程序中加以解析才行。傳統的XML解析方式有DOM和SAX兩種,DOM方式會把整個XML報文讀進來,而且全部節點全被自動加載到一個樹狀結構,之後每一個節點值都到該樹狀結構中讀取。SAX方式不會事先讀入整個XML報文,而是根據節點名稱從報文起點開始掃描,一旦找到該節點的標記頭位置,即刻日後尋找該節點的標記尾,那麼節點標記頭尾之間的數據即是節點值了。單就某個節點值的解析過程而言,加載全部節點的DOM方式顯然較費功夫,從頭順序查找的SAX方式執行效率更高。但若要求同時獲取多個節點的數值,則採起樹狀結構遍歷的DOM方式整體性能更加,而每次都從頭找起的SAX方式無疑作了重複勞動。總之兩種方式的解析效果各有優劣,須要按照實際場景決定取捨。
儘管JDK集成了DOM與SAX的解析工具,其中DOM解析工具封裝在包org.w3c.dom中,SAX解析工具封裝在包javax.xml.parsers中,但是它倆用起來着實費勁,解析過程艱深晦澀,實際開發當中基本不予採用。應用比較多的XML解析工具反而是第三方的Dom4j,Dom4j的解析方式遵循DOM規則,但比起Java自帶的DOM工具要易用得多,其性能也很優異,幾乎成爲Java開發必備的XML解析神器了。經過Dom4j解析XML報文的步驟主要有下列五步:
一、建立SAXReader閱讀器對象;
二、把字符串形式的XML報文轉換爲輸入流對象;
三、命令閱讀器對象從輸入流中讀取Document文檔對象;
四、得到文檔對象的根節點Element;
五、從根節點往下依次解析每一個層級的節點值;
在具體的節點解析過程之中,會頻繁調用Element的相關方法,它的經常使用方法說明以下:
getText:得到當前節點的字符串值。
element:得到當前節點下面指定名稱的子節點對象。
elementText:得到當前節點下面指定名稱的子節點值。
elements:得到當前節點下面指定名稱的子節點清單。
attribute:得到當前節點自身指定名稱的屬性對象。
attributeValue:得到當前節點自身指定名稱的屬性值。
attributes:得到當前節點擁有的所有屬性清單。java
仍之前述的XML報文爲例,下面是採用Dom4j解析該XML串的代碼例子:node
// 經過dom4j解析xml串 private static GoodsOrder testParserByDom4j(String xml) { GoodsOrder order = new GoodsOrder(); // 建立一個購物訂單對象 // 建立SAXReader閱讀器對象 SAXReader reader = new SAXReader(); // 根據字符串構建字節數組輸入流 try (InputStream is = new ByteArrayInputStream(xml.getBytes(CHARSET))) { // 命令閱讀器從輸入流中讀取文檔對象 Document document = reader.read(is); // 得到文檔對象的根節點 Element root = document.getRootElement(); // 獲取根節點下面名叫user_info的節點 Element user_info = root.element("user_info"); // 獲取user_info節點下面名叫name的節點值 order.user_info.name = user_info.element("name").getText(); // 獲取user_info節點下面名叫address的節點值 order.user_info.address = user_info.element("address").getText(); // 獲取user_info節點下面名叫phone的節點值 order.user_info.phone = user_info.element("phone").getText(); System.out.println(String.format("用戶信息以下:姓名=%s,地址=%s,手機號=%s", order.user_info.name, order.user_info.address, order.user_info.phone)); // 獲取根節點下面名叫goods_list的節點清單 List<Element> goods_list = root.element("goods_list").elements(); for (int i=0; i<goods_list.size(); i++) { // 遍歷商品節點清單 Element goods_item = goods_list.get(i); GoodsItem item = new GoodsItem(); // 建立一項商品對象 // 獲取當前商品項節點下面名叫goods_name的節點值 item.goods_name = goods_item.element("goods_name").getText(); // 獲取當前商品項節點下面名叫goods_number的節點值 item.goods_number = Integer.parseInt(goods_item.element("goods_number").getText()); // 獲取當前商品項節點下面名叫goods_price的節點值 item.goods_price = Double.parseDouble(goods_item.element("goods_price").getText()); System.out.println(String.format("第%d個商品:名稱=%s,數量=%d,價格=%f", i+1, item.goods_name, item.goods_number, item.goods_price)); order.goods_list.add(item); // 往商品清單中添加指定商品對象 } } catch (Exception e) { e.printStackTrace(); } return order; // 返回解析後的購物訂單對象 }
運行以上的解析代碼,觀察到如下的購物訂單日誌,可見成功實現了xml串到對象的解析操做:json
用戶信息以下:姓名=思無邪,地址=桃花島水簾洞123號,手機號=15960238696 第1個商品:名稱=Mate30,數量=1,價格=8888.000000 第2個商品:名稱=格力中央空調,數量=1,價格=58000.000000 第3個商品:名稱=紅蜻蜓皮鞋,數量=3,價格=999.000000
除了解析各節點的節點值,Dom4j還能解析各節點的屬性值,若想正常解析指定名稱的屬性值,則需明確下列三個要素:該屬性的上級節點對象、該屬性所在節點的節點名稱,該屬性的屬性名稱。有了這三個要素,便可經過如下方法從指定節點的指定屬性成功獲取屬性值:數組
// 打印指定節點名稱的指定屬性值 private static void printValueAndAttr(Element parent, String node_name, String attr_name) { // 獲取父節點下面指定名稱的子節點 Element element = parent.element(node_name); // 得到子節點的節點值 String node_value = element.getText(); String attr_value = ""; // 根據屬性名稱獲取子節點的對應屬性對象 Attribute attr = element.attribute(attr_name); if (attr != null) { attr_value = attr.getText(); // 獲取該屬性的屬性值 } // 打印子節點的詳細信息,包括節點名稱、節點值、屬性名稱、屬性值 System.out.println(String.format("節點名稱=%s, 節點值=%s, 屬性名稱=%s, 屬性值=%s", node_name, node_value, attr_name, attr_value)); }
接下來在原先的XML解析代碼裏補充以下的一行屬性解析代碼:網絡
// 打印user_info節點的name子節點的type屬性值 printValueAndAttr(user_info, "name", "type");
再次運行XML解析代碼,在輸出的購物訂單日誌中觀察到多了下面這行日誌,表示解析到了name節點的type屬性值:dom
節點名稱=name, 節點值=思無邪, 屬性名稱=type, 屬性值=string
更多Java技術文章參見《Java開發筆記(序)章節目錄》工具