爲了在不同應用軟件、不同平臺、不同操作系統之間實現數據共享,我們需要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);
}
}