在上一篇博客中,我們介紹了什么是 XML ,http://www.cnblogs.com/ysocean/p/6901008.html,那么這一篇博客我們介紹如何來解析 XML 。
部分文檔引用:http://www.cnblogs.com/lanxuezaipiao/archive/2013/05/17/3082949.html
解析 XML 文件一般來說有兩種底層形式,一種是基於樹的結構來解析的稱為DOM;另一種是基於事件流的形式稱為Sax。而在這兩種解析方式的基礎上,基於底層api的更高級封裝解析器也應用而生,比如面向Java的 JDom和 Dom4J。
1、DOM(Document Object Model)
DOM 是用與平台和語言無關的方式表示 XML 文檔的官方 W3C 標准。DOM 是以層次結構組織的節點或信息片斷的集合。這個層次結構允許開發人員在樹中尋找特定信息。分析該結構通常需要加載整個文檔和構造層次結構,然后才能做任何工作。由於它是基於信息層次的,因而 DOM 被認為是基於樹或基於對象的。
優點:
①、整個 Dom 樹都加載到內存中了,所以允許隨機讀取訪問數據。
②、允許隨機的對文檔結構進行增刪。
缺點:
①、整個 XML 文檔必須一次性解析完,耗時。
②、整個 Dom 樹都要加載到內存中,占內存。
適用於:文檔較小,且需要修改文檔內容
2、Sax(Simple API for XML)

SAX處理的特點是基於事件流的。分析能夠立即開始,而不是等待所有的數據被處理。而且,由於應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中。這對於大型文檔來說是個巨大的優點。事實上,應用程序甚至不必解析整個文檔;它可以在某個條件得到滿足時停止解析。sax分析器在對xml文檔進行分析時,觸發一系列的事件,應用程序通過事件處理函數實現對xml文檔的訪問,因為事件觸發是有時序性的,所以sax分析器提供的是一種對xml文檔的順序訪問機制,對於已經分析過的部分,不能再重新倒回去處理.此外,它也不能同時訪問處理2個tag,sax分析器在實現時,只是順序地檢查xml文檔中的字節流,判斷當前字節是xml語法中的哪一部分,檢查是否符合xml語法並且觸發相應的事件.對於事件處理函數的本身,要由應用程序自己來實現. SAX解析器采用了基於事件的模型,它在解析XML文檔的時候可以觸發一系列的事件,當發現給定的tag的時候,它可以激活一個回調方法,告訴該方法制定的標簽已經找到。
優點:
①、訪問能夠立即進行,不需要等待所有數據被加載。
②、只在讀取數據時檢查數據,不需要保存在內存中
③、不需要將整個數據都加載到內存中,占用內存少
④、允許注冊多個Handler,可以用來解析文檔內容,DTD約束等等。
缺點:
①、需要應用程序自己負責TAG的處理邏輯(例如維護父/子關系等),文檔越復雜程序就越復雜。
②、單向導航,無法定位文檔層次,很難同時訪問同一文檔的不同部分數據,不支持XPath。
③、不能隨機訪問 xml 文檔,不支持原地修改xml。
適用於:文檔較大,只需要讀取文檔數據。
3、JDOM(Java-based Document Object Model)
JDOM是處理xml的純java api.使用具體類而不是接口。JDOM具有樹的遍歷,又有SAX的java規則。
JDOM與DOM主要有兩方面不同。首先,JDOM僅使用具體類而不使用接口。這在某些方面簡化了API,但是也限制了靈活性。第二,API大量使用了Collections類,簡化了那些已經熟悉這些類的Java開發者的使用。
JDOM自身不包含解析器。它通常使用SAX2解析器來解析和驗證輸入XML文檔(盡管它還可以將以前構造的DOM表示作為輸入)。它包含一些轉換器以將JDOM表示輸出成SAX2事件流、DOM模型或XML文本文檔。JDOM是在Apache許可證變體下發布的開放源碼。
優點:
①、使用具體類而不是接口,簡化了DOM的API。
②、大量使用了Java集合類,方便了Java開發人員。
缺點:
①、不能處理大於內存的文檔.
②、API 簡單,沒有較好的靈活性
4、DOM4J(Document Object Model for Java)
雖然DOM4J代表了完全獨立的開發結果,但最初,它是JDOM的一種智能分支。它合並了許多超出基本XML文檔表示的功能,包括集成的XPath,支持XML Schema支持以及用於大文檔或流化文檔的基於事件的處理。它還提供了構建文檔表示的選項,它通過DOM4J API和標准DOM接口具有並行訪問功能。從2000下半年開始,它就一直處於開發之中。
為支持所有這些功能,DOM4J使用接口和抽象基本類方法。DOM4J大量使用了API中的Collections類,但是在許多情況下,它還提供一些替代方法以允許更好的性能或更直接的編碼方法。直接好處是,雖然DOM4J付出了更復雜的API的代價,但是它提供了比JDOM大得多的靈活性。
在添加靈活性、XPath集成和對大文檔處理的目標時,DOM4J的目標與JDOM是一樣的:針對Java開發者的易用性和直觀操作。它還致力於成為比JDOM更完整的解決方案,實現在本質上處理所有Java/XML問題的目標。在完成該目標時,它比JDOM更少強調防止不正確的應用程序行為。
DOM4J是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多Java軟件都在使用DOM4J來讀寫XML,特別值得一提的是連Sun的JAXM也在用DOM4J。
優點:
①、大量使用了Java集合類,方便Java開發人員,同時提供一些提高性能的替代方法。
②、支持XPath。查找節點特別快
③、靈活性高。
缺點:
①、大量的使用了接口,API復雜,理解難。
②、移植性差。
注:XPath是一門在 XML 文檔中查找信息的語言。
比較:
1、 DOM4J性能最好,連Sun的JAXM也在用DOM4J。目前許多開源項目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J來讀取XML配置文件。如果不考慮可移植性,那就采用DOM4J。
2、JDOM和DOM在性能測試時表現不佳,在測試10M文檔時內存溢出,但可移植。在小文檔情況下還值得考慮使用DOM和JDOM.雖然JDOM的開發者已經說明他們期望在正式發行版前專注性能問題,但是從性能觀點來看,它確實沒有值得推薦之處。另外,DOM仍是一個非常好的選擇。DOM實現廣泛應用於多種編程語言。它還是許多其它與XML相關的標准的基礎,因為它正式獲得W3C推薦(與基於非標准的Java模型相對),所以在某些類型的項目中可能也需要它(如在JavaScript中使用DOM)。
3、SAX表現較好,這要依賴於它特定的解析方式-事件驅動。一個SAX檢測即將到來的XML流,但並沒有載入到內存(當然當XML流被讀入時,會有部分文檔暫時隱藏在內存中)。
綜上所述:如果XML文檔較大且不考慮移植性問題建議采用DOM4J;如果XML文檔較小則建議采用JDOM;如果需要及時處理而不需要保存數據則考慮SAX。
實例:
第一步:建立一個 student.xml 文件,我們以這個文件通過上面的四種解析方式來對比分析。
<?xml version="1.0" encoding="UTF-8"?> <students> <student> <name>Tom</name> <age>11</age> </student> <student> <name>Bob</name> <age>22</age> </student> <student> <name>Marry</name> <age>23</age> </student> </students>
一、DOM 解析(JDK已經自帶jar包,不需要額外導入!)
package com.ys.xml;
import java.io.FileOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DomParser{
/**
* 解析器讀入整個文檔,然后構建一個駐留內存的樹結構,
* 該方法返回 Document 對象,然后我們可以通過 這個對象來操作文檔
*/
public Document getDocument(String fileName) throws Exception{
//1.創建解析工廠
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//2.得到解析器
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
//3.得到文檔對象
Document document = dBuilder.parse(fileName);
return document;
}
//讀取xml文檔中的數據
public void read(String fileName) throws Exception{
//獲取 Document 對象
Document document = new DomParser().getDocument(fileName);
//獲取<name></name>的節點
NodeList nameNode = document.getElementsByTagName("name");
//獲取<name sex="xxx"></name>節點的sex屬性
Element element = (Element) document.getElementsByTagName("name").item(0);
System.out.println(element.getAttribute("sex"));//xxx
for(int i = 0 ; i < nameNode.getLength() ;i++){
System.out.println(nameNode.item(i).getTextContent());
}
/**結果為
* Tom
* Bob
* Marry
*/
//獲取文檔的根元素對象
Element rootElementName = document.getDocumentElement();
System.out.println(rootElementName.getNodeName()); //students
//得到根節點
Node root = document.getElementsByTagName(rootElementName.getNodeName()).item(0);
list(root);
}
//打印所有標簽
private void list(Node root) {
if(root instanceof Element){
System.out.println(root.getNodeName());
}
NodeList list = root.getChildNodes();
for(int i = 0 ; i < list.getLength() ; i++){
Node child = list.item(i);
list(child);
}
}
//向 xml 文件中增加節點和屬性
public void add(String fileName) throws Exception{
//獲取 Document 對象
Document document = new DomParser().getDocument(fileName);
//創建節點
Element sex = document.createElement("sex");
sex.setTextContent("男");
//把創建的節點添加到第一個<student></student>標簽上
Element student = (Element) document.getElementsByTagName("student").item(0);
student.appendChild(sex);
//在<name></name>中增加屬性 <name address="xxx"></name>
Element name = (Element) document.getElementsByTagName("name").item(0);
name.setAttribute("address", "xxx");
//把更新后的內存寫入xml文檔中
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tFormer = tfFactory.newTransformer();
tFormer.transform(new DOMSource(document),
new StreamResult(new FileOutputStream("src/student.xml")));
}
//向 xml 文件中刪除節點和屬性
public void delete(String fileName) throws Exception{
//獲取 Document 對象
Document document = new DomParser().getDocument(fileName);
//得到要刪除的第一個<name></name>節點
Element name = (Element) document.getElementsByTagName("name").item(0);
//得到要刪除的第一個<name></name>節點的父節點
//Element student = (Element) document.getElementsByTagName("student").item(0);
//student.removeChild(name);
//上面兩步可以簡寫為
name.getParentNode().removeChild(name);
//在<name></name>中刪除屬性 <name address="xxx"></name>
name.removeAttribute("address");
//把更新后的內存寫入xml文檔中
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tFormer = tfFactory.newTransformer();
tFormer.transform(new DOMSource(document),
new StreamResult(new FileOutputStream("src/student.xml")));
}
//向 xml 文件中更新節點和屬性
public void update(String fileName) throws Exception{
//獲取 Document 對象
Document document = new DomParser().getDocument(fileName);
//得到要刪除的第一個<name></name>節點
Element name = (Element) document.getElementsByTagName("name").item(0);
//在<name></name>中更新屬性 <name address="xxx"></name>為<name address="yyy"></name>
name.setAttribute("address", "yyy");
//更新name節點的文字為VAE,即<name>vae</name>
name.setTextContent("vae");
//把更新后的內存寫入xml文檔中
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tFormer = tfFactory.newTransformer();
tFormer.transform(new DOMSource(document),
new StreamResult(new FileOutputStream("src/student.xml")));
}
}
二、SAX 解析(JDK已經自帶jar包,不需要額外導入!)
package com.ys.xml;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class SaxParser{
public static void main(String[] args) throws Exception {
//1.創建解析工廠
SAXParserFactory spFactory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser sParser = spFactory.newSAXParser();
//3.得到讀取器
XMLReader xmlReader = sParser.getXMLReader();
//4.設置內容處理器
xmlReader.setContentHandler(new TagDefaultHandler());
//5.讀取 XML 文檔內容
xmlReader.parse("src/student.xml");
}
}
//第一種方法:繼承接口ContentHandler 得到 XML 文檔所有內容
class ListHandler implements ContentHandler{
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
System.out.println("<"+qName+">");
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
System.out.println(new String(ch,start,length));
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println("</"+qName+">");
}
@Override
public void setDocumentLocator(Locator locator) {
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
}
@Override
public void processingInstruction(String target, String data)
throws SAXException {
}
@Override
public void skippedEntity(String name) throws SAXException {
}
}
//使用繼承類 DefaultHandler 更好
class TagDefaultHandler extends DefaultHandler{
//當前解析的是什么標簽
private String currentTag;
//想獲得第幾個標簽的值
private int tagNumber=0;
//當前解析的是第幾個標簽
private int currentNumber=0;
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
currentTag = qName;
//當前解析的name 標簽是第幾個
if("name".equals(currentTag)){
currentNumber++;
System.out.println(currentNumber);
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
//打印所有name標簽的值
if("name".equals(currentTag)){
System.out.println(new String(ch,start,length));
}
//想獲得 第二個name標簽的值
tagNumber = 2;
if("name".equals(currentTag)&¤tNumber==tagNumber){
System.out.println(new String(ch,start,length));
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
currentTag = null;
}
}
三、DOM4J 解析
JAR包下載鏈接:http://pan.baidu.com/s/1b5L9AA 密碼:wg2l
package com.ys.xml;
import java.io.File;
import java.io.FileOutputStream;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.junit.Test;
public class DOM4JParser {
//讀取第二個<name><name>
@Test
public void read() throws Exception{
SAXReader reader = new SAXReader();
Document document = reader.read(new File("src/student.xml"));
//得到根節點
Element root = document.getRootElement();
//得到第二個<student><student>節點
Element student = (Element)root.elements("student").get(1);
//獲取<name><name>中間的值
String value = student.element("name").getText();
System.out.println(value);//Bob
//獲取<name sex="xxx"><name>中間的sex值
String sexValue = student.element("name").attributeValue("sex");
System.out.println(sexValue);//xxx
}
//增加節點
@Test
public void add() throws Exception{
SAXReader reader = new SAXReader();
Document document = reader.read(new File("src/student.xml"));
Element student = document.getRootElement().element("student");
student.addElement("schoolName").setText("湖北");
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("utf-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("src/student.xml"),format);
writer.write(document);
writer.close();
}
//刪除節點
@Test
public void delete() throws Exception{
SAXReader reader = new SAXReader();
Document document = reader.read(new File("src/student.xml"));
Element student = (Element)document.getRootElement().elements("student").get(1);
student.element("schoolName").setText("湖南");
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("utf-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("src/student.xml"),format);
writer.write(document);
writer.close();
}
//修改節點
@Test
public void update() throws Exception{
SAXReader reader = new SAXReader();
Document document = reader.read(new File("src/student.xml"));
Element student = document.getRootElement().element("student");
Element schoolName = student.element("schoolName");
schoolName.getParent().remove(schoolName);
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("utf-8");
XMLWriter writer = new XMLWriter(new FileOutputStream("src/student.xml"),format);
writer.write(document);
writer.close();
}
}
XPath 簡介:
由於 DOM4J 是支持 XPath,那么 XPath 是什么呢?
XPath 是一門在 XML 文檔中查找信息的語言。使用路徑表達式來選取 XML 文檔中的節點或者節點集。這些路徑表達式和我們在常規的電腦文件系統中看到的表達式非常相似。


我們以一個例子看一下用法:
package com.ys.xml;
import java.io.File;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
public class XPathParser {
public static void main(String[] args) throws Exception {
SAXReader reader = new SAXReader();
Document document = reader.read(new File("src/student.xml"));
//得到第一個 name 節點的值
String nameValue = document.selectSingleNode("//name").getText();
System.out.println(nameValue); //Tom
//得到所有 name 節點的值
List<Node> nameValues = document.selectNodes("//name");
for(Node obj : nameValues){
System.out.println(obj.getText());//Tom Bob Marry
}
}
}
