Java解析與生成XML文檔

爲了在不同應用軟件、不同平臺、不同操作系統之間實現數據共享,我們需要XML文件來進行數據的儲存和傳輸。

如下所示爲一個xml文件內容,定義了一個書店,包含兩本書的信息

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
	<book id="1">
		<name>冰與火之歌</name>
		<author>喬治馬丁</author>
		<year>2014</year>
		<price>89</price>
	</book>
	<book id="2">
		<name>安徒生童話</name>
		<year>2004</year>
		<price>77</price>
		<language>English</language>
	</book>
</bookstore>

1、解析XML文件

DOM解析

DOM是XML官方提供的與平臺無關的解析方式,DOM會一次性將XML文檔讀入內存並形成DOM樹結構,當XML文檔過大時對內存消耗較大,而且讀取速度受影響。

值得注意的是DOM不僅將一個<tag></tag>視爲一個Node節點,而且節點的屬性以及換行的空白也視爲Node類型,所以在第一個<book>節點調用childNode.getLength()方法時返回子節點數爲9,即包括四個<tag>和5個回車換行空白,但是二者的NodeType不同,可以根據類型進行區分兩種節點。

同理<name></name>之間的文字也被視爲節點,所以要獲取其中的文字需要獲取name節點的子節點然後再獲取文本值。也可以通過getText(Content)方法來獲取name節點間的文本內容,效果相同。

import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;

public class DOMParse {
    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
        // 1、創建DocumentBuilderFactory對象
        DocumentBuilderFactory  dbf=DocumentBuilderFactory.newInstance();
        // 2、創建DocumentBuilder對象
        DocumentBuilder db=dbf.newDocumentBuilder();
        // 3、加載xml文件爲document對象
        Document doc=db.parse("src/com/xml/books.xml");
        // 獲取所有book節點集合
        NodeList bookList=doc.getElementsByTagName("book");
        int num=bookList.getLength();       // 獲取節點集合的長度
        for (int i = 0; i < num; i++) {
            Node book=bookList.item(i);     // 獲取集合中的一個節點
            NamedNodeMap attrs=book.getAttributes();        // 獲取節點的所有屬性
            for (int j = 0; j < attrs.getLength(); j++) {       // 遍歷節點的屬性集
                Node attr=attrs.item(j);        // 獲取屬性集中的一個屬性
                System.out.println(attr.getNodeName());     // 輸出屬性名
                System.out.println(attr.getNodeValue());        // 輸出屬性值
            }

            // 知道具體屬性名時可直接通過屬性名獲取屬性值
            Element bookElement=(Element)book;      // 此時屬性類型從Node轉換爲Element
            String s=bookElement.getAttribute("id");
            System.out.println(s);

            // 遍歷book的所有子節點
            NodeList childNodes=book.getChildNodes();
            for (int j = 0; j < childNodes.getLength(); j++) {
                Node childNode=childNodes.item(j);
                // 根據Node類型區分元素節點和文本節點
                if (childNode.getNodeType()==Node.ELEMENT_NODE){
                    System.out.println("節點名:"+ childNode.getNodeName());
                    // 通過取子節點得到文本值
                    System.out.println("值爲:"+childNode.getFirstChild().getNodeValue());
                    // 直接獲取文本值
                    System.out.println("文本爲:"+ childNode.getTextContent());
                }
            }
        }
    }
}

2、SAX解析

SAX是Java官方提供的基於事件驅動的XML解析方式,與DOM加載整篇xml文檔解析的方式不同,SAX是邊加載XML文檔邊解析,對內存耗費小。但與DOM樹結構相比,SAX訪問數據沒有結構,不便於理解和編碼,而且由於其方法異步執行,很難同時訪問不同數據。

使用SAX過程如下,首先創建一個Factory實例,再通過它創建一個parser實例,之後創建一個繼承自DefaultHandler類的handler對象,然後通過parser.parse()進行文檔解析,傳入兩個參數,第一個是文檔路徑,第二個爲handler。

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // 1、獲取SAXParserFactory實例
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // 2、獲取Parser實例
        SAXParser parser = factory.newSAXParser();
        // 3、創建handler實例
        SAXHandler saxHandler=new SAXHandler();
        // 4、解析文檔
        parser.parse("src/com/xml/books.xml",saxHandler);
    }

 handler從文檔開始遍歷,依次調用調用startDocument()、startElement()、characters()、endElement()、endDocument()方法。

startElement()方法在遇到開始標籤例如<book>、<author>時觸發,其參數qName爲標籤名,attributes爲標籤的屬性數組

characters()方法在遇到標籤之間的字符串時觸發,可以定義對字符串的的操作,這些字符串也包括無意義的空白換行

endELement()方法在遇到結束標籤</book>時觸發

如果需要在以上各個方法之間共享變量和參數,可以通過定義全局變量來實現

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXHandler extends DefaultHandler {
    @Override       // 文檔開始執行
    public void startDocument() throws SAXException {
        super.startDocument();
    }

    @Override       // 開始標籤執行
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        if (qName.equals("book")){      // 解析<book>節點的屬性
            System.out.println("======book解析開始======");
            // 根據具體屬性名id獲取屬性值
            String value=attributes.getValue("id");
            System.out.println("book的屬性值:"+value);
            // 未知屬性值,遍歷所有屬性
            int num=attributes.getLength();
            for (int i = 0; i < num; i++) {
                System.out.print(attributes.getQName(i)+" : ");     // 獲取屬性名
                System.out.print(attributes.getValue(i)+"---");     // 獲取屬性值
            }
        }else {
            System.out.print(qName+" : ");
        }
    }

    @Override        // 遇到字符
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        String s=new String(ch,start,length);
        if (!s.trim().equals("")){      // 排除換行字符串節點
            System.out.print(s+" ");
        }
    }

    @Override       // 結束標籤
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if (qName.equals("book")) {
            System.out.println();
            System.out.println("======book解析結束======");
        }
    }

    @Override       // 文檔結束執行
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

 運行結果如下:

3、JDOM

在SAX基礎上,第三方開源組織編寫了JDOM與DOM4J兩種解析方式,因此在使用JDOM時首先需要首先下載和導入jar包。

通過JDOM的getChildren()方法可以很容易的獲取標籤的子節點並進行遍歷

如下所示從XML中讀取到的書籍內容保存到Book對象中,並將對象存儲到ArrayList列表中

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import com.imooc.entity.Book;        // 引入創建的Book對象


public class JDOMTest {
    // 創建用於保存Book對象的列表
    private static ArrayList<Book> booksList = new ArrayList<Book>();

    public static void main(String[] args) {
        // 進行對books.xml文件的JDOM解析
        // 準備工作
        // 1.創建一個SAXBuilder的對象
        SAXBuilder saxBuilder = new SAXBuilder();
        InputStream in;
        try {
            // 2.創建一個輸入流,將xml文件加載到輸入流中
            in = new FileInputStream("src/res/books.xml");
            InputStreamReader isr = new InputStreamReader(in, "UTF-8");
            // 3.通過saxBuilder的build方法,將輸入流加載到saxBuilder中
            Document document = saxBuilder.build(isr);
            // 4.通過document對象獲取xml文件的根節點
            Element rootElement = document.getRootElement();
            // 5.獲取根節點下的子節點的List集合,並進行遍歷
            List<Element> bookList = rootElement.getChildren();
            for (Element book : bookList) {
                Book bookEntity = new Book();
                System.out.println("======開始解析第" + (bookList.indexOf(book) + 1)+ "書======");
                // 解析book的屬性集合
                List<Attribute> attrList = book.getAttributes();
                // //知道節點的屬性名稱時,獲取節點值的方法
                // book.getAttributeValue("id");
                // 遍歷attrList(針對不清楚book節點下屬性的名字及數量)
                for (Attribute attr : attrList) {
                    // 獲取屬性名
                    String attrName = attr.getName();
                    // 獲取屬性值
                    String attrValue = attr.getValue();
                    System.out.println("屬性名:" + attrName + "----屬性值:"+ attrValue);
                    if (attrName.equals("id")) {
                        bookEntity.setId(attrValue);
                    }
                }
                // 對book節點的子節點的節點名以及節點值的遍歷,並將信息保存到Book對象
                List<Element> bookChilds = book.getChildren();
                for (Element child : bookChilds) {
                    System.out.println("節點名:" + child.getName() + "----節點值:"	+ child.getValue());
                    if (child.getName().equals("name")) {
                        bookEntity.setName(child.getValue());
                    }
                    else if (child.getName().equals("author")) {
                        bookEntity.setAuthor(child.getValue());
                    }
                    else if (child.getName().equals("year")) {
                        bookEntity.setYear(child.getValue());
                    }
                    else if (child.getName().equals("price")) {
                        bookEntity.setPrice(child.getValue());
                    }
                    else if (child.getName().equals("language")) {
                        bookEntity.setLanguage(child.getValue());
                    }
                }
                System.out.println("======結束解析第" + (bookList.indexOf(book) + 1)+ "書======");
                booksList.add(bookEntity);
                bookEntity = null;
                System.out.println(booksList.size());
                // 輸出保存在列表中的第一個對象的id和name
                System.out.println(booksList.get(0).getId());
                system.out.println(booksList.get(0).getName());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

4、DOM4J

DOM4J也是第三方開源的工具包,與JDOM相比擁有更快的執行速度和靈活性

可以通過elementIterator()方法獲取標籤的子節點迭代器,從而實現對節點的遍歷,其使用方法如下:

package com.imooc.dom4jtest;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class DOM4JTest {
    public static void main(String[] args) {
        // 創建SAXReader的對象reader
        SAXReader reader = new SAXReader();
        try {
            // 通過reader對象的read方法加載books.xml文件,獲取docuemnt對象。
            Document document = reader.read(new File("src/res/books.xml"));
            // 通過document對象獲取根節點bookstore
            Element bookStore = document.getRootElement();
            // 通過element對象的elementIterator方法獲取迭代器
            Iterator it = bookStore.elementIterator();
            // 遍歷迭代器,獲取根節點中的信息(書籍)
            while (it.hasNext()) {
                System.out.println("=====開始遍歷某一本書=====");
                Element book = (Element) it.next();
                // 獲取book的屬性名以及 屬性值
                List<Attribute> bookAttrs = book.attributes();
                for (Attribute attr : bookAttrs) {
                    System.out.println("屬性名:" + attr.getName() + "--屬性值:"
                            + attr.getValue());
                }
                Iterator itt = book.elementIterator();
                while (itt.hasNext()) {
                    Element bookChild = (Element) itt.next();
                    System.out.println("節點名:" + bookChild.getName() + "--節點值:" + bookChild.getStringValue());
                }
                System.out.println("=====結束遍歷某一本書=====");
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}

2、生成XML

DOM生成

DOM會在內存中創建DOM樹來暫存節點結構,因此如果需要頻繁修改DOM結構的話使用DOM比SAX更合適,但相應地,其創建速度是四種方法中最慢的,SAX方法則是最快的。

使用DOM首先還是需要生成document對象,然後通過document創建節點,之後使用父節點添加子節點。

最後通過Transformer對象的transform()方法生成XML文件,transform()需要兩個參數,第一個爲要輸出的源文檔,第二個爲輸出文件流

    public static void main(String[] args) throws ParserConfigurationException, TransformerException {
        // 通過嵌套定義創建一個DOM文檔對象doc
        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
        DocumentBuilder db=dbf.newDocumentBuilder();
        Document doc=db.newDocument();

        // 通過doc創建節點
        Element bookstore=doc.createElement("bookstore");
        Element book=doc.createElement("book");
        book.setAttribute("id","1");        // 爲節點添加屬性
        Element name=doc.createElement("name");
        name.setTextContent("百年孤獨");            // 添加節點之間的文本內容
        // 將子節點添加到父節點
        book.appendChild(name);
        bookstore.appendChild(book);
        doc.appendChild(bookstore);

        // 通過Transformer對象輸出xml文件
        TransformerFactory tff=TransformerFactory.newInstance();
        Transformer tf=tff.newTransformer();
        tf.setOutputProperty(OutputKeys.INDENT,"yes");      // 設置輸出xml文件換行
        DOMSource domSource=new DOMSource(doc);                     // 要輸出的文檔對象
        StreamResult streamResult=new StreamResult(new File("src/com/xml/DOMout.xml")); // 輸出的目標文件
        tf.transform(domSource,streamResult);
    }

SAX生成

使用SAX生成同樣需要首先通過嵌套定義生成一個handler與Transformer對象,通過transformer可以對輸出文件進行設置,通過handler可以對文檔進行操作。在操作文檔之前需要將handler和輸出文件流進行關聯。

之後通過handler調用相關方法對文檔進行編輯,像tag標籤一樣,startElement/endElement方法成對使用。通過attr對象來設置和添加標籤的屬性。通過characters()方法來添加標籤之間的文本內容。

    public static void main(String[] args) throws TransformerConfigurationException, FileNotFoundException, SAXException {
        // 生成SAX的Transformer對象
        SAXTransformerFactory stf=(SAXTransformerFactory)SAXTransformerFactory.newInstance();
        TransformerHandler handler=stf.newTransformerHandler();
        Transformer transformer=handler.getTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");     // 設置輸出屬性

        // 創建Result對象並與handler關聯
        Result result=new StreamResult(new FileOutputStream(new File("src/com/xml/SAXout.xml")));
        handler.setResult(result);

        // 通過handler編輯xml文件
        handler.startDocument();
        AttributesImpl attr=new AttributesImpl();       // 創建用於編輯節點屬性的attr對象
        handler.startElement("","","bookstore",attr);       // 結點開始
        attr.clear();       // 每次新設置一個屬性attr之前都需要清空之前的
        attr.addAttribute("","","id","","1");   // 定義attr並添加到節點
        handler.startElement("","","book",attr);
        attr.clear();
        handler.startElement("","","name",attr);
        String s="平凡的世界";
        handler.characters(s.toCharArray(),0,s.length());       // 向節點間添加文本
        handler.endElement("","","name");       // 結束節點
        handler.endElement("","","book");
        handler.endElement("","","bookstore");
        handler.endDocument();
    }

DOM4J生成

DOM4J創建速度雖然慢於SAX,但是快於JDOM,而且其代碼較爲簡潔,是開發中常用的解析與生成方式。

DOM4J創建document對象之後可以直接添加節點,而且由於是封裝好的庫,代碼調用相比之前SAX與DOM方式來說十分簡潔。節點的創建和屬性的添加等操作只需要一行代碼。通過XMLWriter將文檔輸出,writer()接受兩個參數,第一個爲文檔輸出流,第二個可選參數爲format對象,通過format可以自動設置好輸出的回車換行等空格。

package com.xml;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class DOM4jGenerator {
    public static void main(String[] args) throws IOException {
        // 創建DOM4J文檔對象
        Document document=DocumentHelper.createDocument();
        // 從上到下創建節點
        Element bookstore=document.addElement("bookstore");     // 創建並添加子節點
        Element book=bookstore.addElement("book");
        book.addAttribute("id","1");                        // 添加節點屬性
        Element name=book.addElement("name");
        name.setText("冰與火之歌");              // 設置標籤之間的文本

        OutputFormat format= OutputFormat.createPrettyPrint();      // 設置輸出文檔的格式
        format.setEncoding("UTF-8");

        // 將文檔輸出
        FileOutputStream outputStream=new FileOutputStream(new File("src/com/xml/DOM4Jout.xml"));
        XMLWriter writer=new XMLWriter(outputStream, format);
        writer.write(document);
        writer.close();
    }
}

JDOM生成

和DOM4J一樣JDOM也是庫,不過它需要先new節點,然後通過父節點調用addContent()添加子節點

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class JDOMGenerator {
    public static void main(String[] args) throws IOException {
        Element bookstore = new Element("bookstore");        //創建根節點
        Document document = new Document(bookstore);          // 將根節點添加到document對象

        // 創建並添加子節點
        Element book = new Element("book");
        book.setAttribute("id", "1");    // 爲節點添加屬性
        bookstore.addContent(book);                 // 父節點添加子節點
        Element name = new Element("name");
        name.setText("冰與火之歌");
        book.addContent(name);

        // 輸出文檔
        Format format = Format.getPrettyFormat();        // 設置輸出格式
        format.setEncoding("GBK");
        XMLOutputter outputter = new XMLOutputter(format);      // 可以傳入格式設置對象作爲參數
        FileOutputStream outputStream = new FileOutputStream(new File("src/com/xml/JDOMout.xml"));
        outputter.output(document, outputStream);
    }
}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章