首頁  >  文章  >  类库下载  >  Java基礎系列17:使用DOM、SAX、JDOM、DOM4J解析XML檔詳解

Java基礎系列17:使用DOM、SAX、JDOM、DOM4J解析XML檔詳解

高洛峰
高洛峰原創
2016-10-13 15:57:121559瀏覽

一 簡介

在Java中,可以用多種方式來解析XML文件,其中最常見的可能就是DOM、SAX、JDOM、DOM4J這四種方式了。其中,DOM和SAX這兩種解析XML檔案的方式都有jdk自帶的API,因此不需要額外引進第三方的jar包。與之相反的是,JDOM和DOM4J這兩種解析方式都是第三方開源項目,因此在使用這兩種方式解析XML文件時需要額外引入相關jar包

(1)DOM

DOM是用與平台和語言無關的方式表示XML文件的官方W3C標準。 DOM是以層次結構組織的節點或資訊片段的集合,這個層次結構允許開發人員在樹中尋找特定資訊。分析該結構通常需要載入整個文件和構造層次結構,然後才能做任何工作

因此在使用DOM這種方式來解析XML文件時,解析器需要將整個XML文件讀到記憶體中,形成一個樹形結構方便後面的操作

優點: 整個文檔樹在內存中,便於操作;支援刪除、修改、重新排列等多種操作

缺點: 將整個文檔調入內存(包括無用的節點),浪費時間和內存,如果XML過大容易出現記憶體溢位問題

(2)SAX

由於DOM解析XML文件時需要一次性讀入整個文件,當文件過大時有諸多不足之處,因此為了解決這個問題,出現了SAX這種基於事件驅動的解析方式

SAX解析XML文件透過從上往下依序不斷載入內容到記憶體中,當解析器發現元素的開始標誌、結束標誌、文字、文件的開始標誌、文檔的結束標誌等相關標誌時,將會觸發一些對應的事件,我們需要做的就是在這些事件的方法中編寫自訂程式碼,用於保存獲取到的資料

優點:不用事先載入整個文檔,佔用資源(內存)少;使用SAX方式解析編寫的程式碼要比使用DOM解析編寫的程式碼少

缺點:不是持久的;事件過後,若沒保存數據,那麼數據就丟了;無狀態性;從事件中只能得到文本,但不知該文本屬於哪個元素

(3)JDOM

使用JDOM來解析XML檔案跟使用DOM來解析從程式碼上來說解析思路是差不多的。 JDOM與DOM主要有兩方面不同:首先,JDOM僅使用具體類別而不使用接口,這在某些方面簡化了API,但是也限制了靈活性。其次是JDOM的API大量使用了Collections類,簡化了那些已經熟悉這些類別的Java開發者的使用

優點:開源專案;比DOM容易理解

缺點:JDOM本身不包含解析器。它通常使用SAX2解析器來解析和驗證輸入XML文件

(4)DOM4J

DOM4J 是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放原始碼的軟體。如今你可以看到越來越多的Java 軟體都在使用DOM4J 來讀寫XML

由於DOM4J無論在性能方面還是代碼編寫方面都是很強大的,特別是當XML文件很大時使用DOM4J來解析也會有較高的效率。因此,建議平時需要解析XML檔可以考慮盡可能使用DOM4J來解析。當然如果文件非常小的話使用DOM來解析也是可以的

優點:

開源專案

DOM4J是JDOM的一種智慧分支,它合併了需要超出基本XML文件的功能

具有性能優異、靈活性好、簡單易用等特點

二DOM解析XML文件

(1)在進行程式碼編寫測試之前,需要準備一個XML文件,我這裡準備的文件是:demo1.xml

demo1.xml:



    
        zifangsky
        10
        male
        https://www.zifangsky.cn
    
    
        admin
        20
        male
        https://www.tar.pub
    

(2)程式碼實例:

package cn.zifangsky.xml;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
 
public class DomParseTest {
 
    public static void main(String[] args) {
        DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
            // 加载一个xml文件
            Document document = dBuilder
                    .parse("src/cn/zifangsky/xml/demo1.xml");
            // 获取user节点集合
            NodeList userList = document.getElementsByTagName("user");
            int userListLength = userList.getLength();
            System.out.println("此xml文件一共有" + userListLength + "个'user'节点\n");
 
            // 遍历
            for (int i = 0; i < userListLength; i++) {
                // 通过item方法获取指定的节点
                Node userNode = userList.item(i);
 
                // *********************解析属性***********************
 
                // 获取该节点的所有属性值,如:id="1"
                NamedNodeMap userAttributes = userNode.getAttributes();
                System.out.println("'user'节点" + i + "有"
                        + userAttributes.getLength() + "个属性:");
                /**
                 * 1 在不清楚有哪些属性的情况下可以遍历所有属性,
                 * 并获取每个属性对应的属性名和属性值
                 * */
                for (int j = 0; j < userAttributes.getLength(); j++) {
                    // 'user'节点的每个属性组成的节点
                    Node attrnNode = userAttributes.item(j);
                    System.out.println("属性" + j + ": 属性名: "
                            + attrnNode.getNodeName() + " ,属性值: "
                            + attrnNode.getNodeValue());
                }
                /**
                 * 2 在知道有哪些属性值的情况下,可以获取指定属性名的属性值
                 * */
                Element userElement = (Element) userList.item(i);
                System.out.println("属性为'id'的对应值是: "
                        + userElement.getAttribute("id"));
 
                // *********************解析子节点************************
                NodeList childNodes = userNode.getChildNodes();
                System.out.println("\n该节点一共有" + childNodes.getLength()
                        + "个子节点,分别是:");
 
                // 遍历子节点
                for (int k = 0; k < childNodes.getLength(); k++) {
                    Node childNode = childNodes.item(k);
                    // 从输出结果可以看出,每行后面的换行符也被当做了一个节点,因此是:4+5=9个子节点
                    // System.out.println("节点名: " + childNode.getNodeName() +
                    // ",节点值: " + childNode.getTextContent());
                    // 仅取出子节点中的'ELEMENT_NODE',换行符组成的Node是'TEXT_NODE'
                    if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                        // System.out.println("节点名: " + childNode.getNodeName()
                        // + ",节点值: " + childNode.getTextContent());
                        // 最低一层是文本节点,节点名是'#text'
                        System.out.println("节点名: " + childNode.getNodeName()
                                + ",节点值: "
                                + childNode.getFirstChild().getNodeValue());
                    }
                }
 
                System.out.println("***************************");
            }
 
        } catch (Exception e) {
            e.printStackTrace();
        }
 
    }
 
}

從上面的程式碼可以看出,在使用DOM來解析XML檔案時一般需要做以下幾步操作:

建立一個文件建構器工廠(DocumentBuilderFactory)實例

透過上面的DocumentBuilderFactory產生一個新的文檔建構器(DocumentBuilder)

使用上面的DocumentBuilder解析(parse)一個XML文件,產生文檔樹(Document)

透過Document取得指定id的節點或根據節點名取得所有符合條件的節點集合

遍歷每個節點,可以取得該節點的屬性、屬性值等相關參數

如果該節點還存在子節點,可以根據上面的方式繼續遍歷它的所有子節點

(3)上面的程式碼輸出如下:

此xml文件一共有2个'user'节点
 
'user'节点0有1个属性:
属性0: 属性名: id ,属性值: 1
属性为'id'的对应值是: 1
 
该节点一共有9个子节点,分别是:
节点名: name,节点值: zifangsky
节点名: age,节点值: 10
节点名: sex,节点值: male
节点名: contact,节点值: https://www.zifangsky.cn
***************************
'user'节点1有1个属性:
属性0: 属性名: id ,属性值: 2
属性为'id'的对应值是: 2
 
该节点一共有9个子节点,分别是:
节点名: name,节点值: admin
节点名: age,节点值: 20
节点名: sex,节点值: male
节点名: contact,节点值: https://www.tar.pub
***************************

三SAX解析XML檔

在进行本次测试时,并不引入其他XML文件,仍然使用上面的demo1.xml文件

由于SAX解析XML文件跟DOM不同,它并不是将整个文档都载入到内存中。解析器在解析XML文件时,通过逐步载入文档,从上往下一行行的解析XML文件,在碰到文档开始标志、节点开始标志、文本文档、节点结束标志、文档结束标志时进行对应的事件处理。因此,我们首先需要构造一个这样的解析处理器来申明:当解析到这些标志时,我们需要进行怎样的自定义处理

(1)解析处理器SAXParseHandler.java:

package cn.zifangsky.xml;
 
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
 
public class SAXParseHandler extends DefaultHandler {
 
    /**
     * 用来遍历XML文件的开始标签
     * */
    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
         
        //解析'user'元素的属性值
//      if(qName.equals("user"))
//          System.out.println("'user'元素的id属性值是:" + attributes.getValue("id"));
         
        //遍历并打印元素的属性
        int length = attributes.getLength();
        if(length > 0){
            System.out.println("元素'" + qName + "'的属性是:");
             
            for(int i=0;i");
 
    }
 
    /**
     * 用来遍历XML文件的结束标签
     * */
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        super.endElement(uri, localName, qName);
         
        System.out.println("<" + qName + "/>");
    }
     
    /**
     * 文本内容
     * */
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        super.characters(ch, start, length);
        String value = new String(ch, start, length).trim();
        if(!value.equals(""))
            System.out.print(value);
    }
 
    /**
     * 用来标识解析开始
     * */
    @Override
    public void startDocument() throws SAXException {
        System.out.println("SAX解析开始");
        super.startDocument();
    }
     
    /**
     * 用来标识解析结束
     * */
    @Override
    public void endDocument() throws SAXException {
        System.out.println("SAX解析结束");
        super.endDocument();
    }
 
}

关于上面代码的一些含义我这里就不再做解释了,可以自行参考注释内容

(2)测试:

SAXParseTest.java文件:

package cn.zifangsky.xml;
 
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
 
public class SAXParseTest {
 
    public static void main(String[] args) {
        SAXParserFactory sFactory = SAXParserFactory.newInstance();
        try {
            SAXParser saxParser = sFactory.newSAXParser();
            //创建自定义的SAXParseHandler解析类
            SAXParseHandler saxParseHandler = new SAXParseHandler();
            saxParser.parse("src/cn/zifangsky/xml/demo1.xml", saxParseHandler);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}

从上面的代码可以看出,使用SAX解析XML文件时,一共传递进去了两个参数,分别是:XML文件路径和前面定义的解析处理器。有了具体的XML文件以及对应的处理器来处理对应的标志事情,因此SAX这种解析方式就可以顺利地进行解析工作了

(3)上面测试的输出如下:

SAX解析开始
元素'user'的属性是:
    属性名:id,属性值: 1
 
zifangsky
10
male
https://www.zifangsky.cn

元素'user'的属性是:
    属性名:id,属性值: 2
 
admin
20
male
https://www.tar.pub


SAX解析结束

四 JDOM解析XML文件

跟前面两种解析方式不同的是,使用JDOM来解析XML文件需要下载额外的jar包

(1)下载jar包并导入到项目中:

下载地址:http://www.jdom.org/downloads/index.html

目前最新版本是:JDOM 2.0.6

然后将下载得到的“jdom-2.0.6.jar”文件导入到测试项目中

注:关于如何在一个Java项目中导入额外的jar,这里将不多做解释,不太会的童鞋可以自行百度

(2)测试代码:

JDOMTest.java:

package cn.zifangsky.xml;
 
import java.util.List;
 
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
 
public class JDOMTest {
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        SAXBuilder saxBuilder = new SAXBuilder();
        try {
            Document document = saxBuilder.build("src/cn/zifangsky/xml/demo1.xml");
             
            //获取XML文件的根节点
            Element rootElement = document.getRootElement();
//          System.out.println(rootElement.getName());
            List usersList = rootElement.getChildren();  //获取子节点
            for(Element u : usersList){
//              List attributes = u.getAttributes();
//              for(Attribute attribute : attributes){
//                  System.out.println("属性名:" + attribute.getName() + ",属性值:" + attribute.getValue());
//              }
                System.out.println("'id'的值是: " + u.getAttributeValue("id"));
            }
             
             
        }catch (Exception e) {
            e.printStackTrace();
        }
 
    }
 
}

从上面的代码可以看出,使用JDOM来解析XML文件,主要需要做以下几个步骤:

新建一个SAXBuilder

通过SAXBuilder的build方法传入一个XML文件的路径得到Document

通过Document的getRootElement方法获取根节点

通过getChildren方法获取根节点的所有子节点

然后是遍历每个子节点,获取属性、属性值、节点名、节点值等内容

如果该节点也有子节点,然后同样可以通过getChildren方法获取该节点的子节点

后面的步骤跟上面一样,不断递归到文本节点截止

(3)上面测试的输出如下:

'id'的值是: 1
'id'的值是: 2

五 DOM4J解析XML文件

jar包下载地址:https://sourceforge.net/projects/dom4j/files/

同样,在使用DOM4J解析XML文件时需要往项目中引入“dom4j-1.6.1.jar”文件

(1)一个简单实例:

i)DOM4JTest.java:

package cn.zifangsky.xml;
 
import java.io.File;
import java.util.Iterator;
import java.util.List;
 
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
 
public class DOM4JTest {
 
    public static void main(String[] args) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(new File("src/cn/zifangsky/xml/demo1.xml"));
            //获取XML文件的根节点
            Element rootElement = document.getRootElement();
            System.out.println(rootElement.getName());
             
            //通过elementIterator方法获取迭代器
            Iterator iterator = rootElement.elementIterator();
            //遍历
            while(iterator.hasNext()){
                Element user = iterator.next();
                //获取属性并遍历
                List aList = user.attributes();
             
                for(Attribute attribute : aList){
                    System.out.println("属性名:" + attribute.getName() + ",属性值:" + attribute.getValue());
                }
                 
                //子节点
                Iterator childList = user.elementIterator();
                while(childList.hasNext()){
                    Element child = childList.next();
//                  System.out.println(child.getName() + " : " + child.getTextTrim());
                    System.out.println(child.getName() + " : " + child.getStringValue());
                }
            }
         
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}

从上面的代码可以看出,跟前面的JDOM解析方式流程是差不多的,并且关键地方也有注释,因此这里就不多做解释了

ii)上面的代码输出如下:

employees
属性名:id,属性值:1
name : zifangsky
age : 10
sex : male
contact : https://www.zifangsky.cn
属性名:id,属性值:2
name : admin
age : 20
sex : male
contact : https://www.tar.pub

(2)将XML文件解析成Java对象:

i)为了方便测试,这里准备一个新的XML文件:

demo2.xml:



    zifangsky
    100
    
    https://www.zifangsky.cn
    旺财
    九头猫妖

ii)同时准备一个Java实体类,恰好跟上面的XML文件中的属性相对应:

User.java:

package cn.zifangsky.xml;
 
import java.util.List;
 
public class User {
    private String name;
    private String sex;
    private int age;
    private String contact;
    private List ownPet;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getSex() {
        return sex;
    }
 
    public void setSex(String sex) {
        this.sex = sex;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getContact() {
        return contact;
    }
 
    public void setContact(String contact) {
        this.contact = contact;
    }
 
    protected List getOwnPet() {
        return ownPet;
    }
 
    protected void setOwnPet(List ownPet) {
        this.ownPet = ownPet;
    }
 
    @Override
    public String toString() {
        return "User [name=" + name + ", sex=" + sex + ", age=" + age
                + ", contact=" + contact + ", ownPet=" + ownPet + "]";
    }
}

iii)测试代码:

XMLtoJava.java:

package cn.zifangsky.xml;
 
import java.io.File;
import java.util.ArrayList;
import java.util.List;
 
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
 
public class XMLtoJava {
 
    public User parseXMLtoJava(String xmlPath){
        User user = new User();
        List ownPet = new ArrayList();
         
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new File(xmlPath));
            Element rootElement = document.getRootElement();  //获取根节点
             
            List children = rootElement.elements();  //获取根节点的子节点
            //遍历
            for(Element child : children){
                String elementName = child.getName();  //节点名
                String elementValue = child.getStringValue();  //节点值
                switch (elementName) {
                case "name":
                    user.setName(elementValue);
                    break;
                case "sex":
                    user.setSex(elementValue);
                    break; 
                case "age":
                    user.setAge(Integer.valueOf(elementValue));
                    break;
                case "contact":
                    user.setContact(elementValue);
                    break; 
                case "ownPet":
                    ownPet.add(elementValue);
                    break; 
                default:
                    break;
                }
     
            }
            user.setOwnPet(ownPet);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return user;
    }
     
    public static void main(String[] args) {
        XMLtoJava demo = new XMLtoJava();
        User user = demo.parseXMLtoJava("src/cn/zifangsky/xml/demo2.xml");
        System.out.println(user);
    }
 
}

经过前面的分析之后,上面这个代码也是很容易理解的:通过遍历节点,如果节点名跟Java类中的某个属性名相对应,那么就将节点值赋值给该属性

iv)上面的代码输出如下:

User [name=zifangsky, sex=男, age=100, contact=https://www.zifangsky.cn, ownPet=[旺财, 九头猫妖]]

(3)解析一个XML文件并尽可能原样输出:

DOM4JTest2:

package cn.zifangsky.xml;
 
import java.io.File;
import java.util.List;
 
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
 
public class DOM4JTest2 {
 
    /**
     * 解析XML文件并尽可能原样输出
     * 
     * @param xmlPath
     *            待解析的XML文件路径
     * @return null
     * */
    public void parse(String xmlPath) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new File(xmlPath));
            Element rootElement = document.getRootElement();
 
            print(rootElement, 0);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 打印一个XML节点的详情
     * 
     * @param element
     *            一个XML节点
     * @param level
     *            用于判断xml节点前缩进多少的标识,每深入一层则多输出4个空格
     * @return null
     * */
    public void print(Element element, int level) {
        List elementList = element.elements(); // 当前节点的子节点List
 
        // 空格
        StringBuffer spacebBuffer = new StringBuffer("");
        for (int i = 0; i < level; i++)
            spacebBuffer.append("    ");
        String space = spacebBuffer.toString();
 
        // 输出开始节点及其属性值
        System.out.print(space + "<" + element.getName());
        List attributes = element.attributes();
        for (Attribute attribute : attributes)
            System.out.print(" " + attribute.getName() + "=\""
                    + attribute.getText() + "\"");
 
        // 有子节点
        if (elementList.size() > 0) {
            System.out.println(">");
            // 遍历并递归
            for (Element child : elementList) {
                print(child, level + 1);
            }
            // 输出结束节点
            System.out.println(space + "");
 
        } else {
            // 如果节点没有文本则简化输出
            if (element.getStringValue().trim().equals(""))
                System.out.println(" />");
            else
                System.out.println(">" + element.getStringValue() + "");
        }
 
    }
 
    public static void main(String[] args) {
        DOM4JTest2 test2 = new DOM4JTest2();
        test2.parse("src/cn/zifangsky/xml/demo3.xml");
 
    }
 
}

这段代码同样没有什么新的东西,原理就是利用递归来不断进行解析输出,注意一下不同层次的节点的缩进即可。刚开始测试时建议用一些结构比较简单的代码,如上面的demo1.xml和demo2.xml文件。在测试没问题时可以选择一些复杂的XML文件来测试是否能够正常输出,比如:

demo3.xml:



    
    
        
            
                
                    
                        
                    
                    
                        
                            
                        
                        
                            
                        
                    
                
            
            
                
                    
                        
                            
                        
                    
                    
                        
                        
                    
                
            
        
    

为什么我在标题上说的是尽可能原样输出,其原因就是上面那段解析代码在碰到下面这种XML节点时,输出就不一样了:




这段XML文档节点最后输出如下:

admin
运维
zabbix
端口


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn