詳解Java中的XML解析
前言
XML,全稱Extensibible Markup Language, 主要用于數(shù)據(jù)的保存或者文件傳輸,其主要特性如下所示:
- 以標(biāo)簽為主的標(biāo)記語(yǔ)言
- 支持自定義標(biāo)簽,支持自我解釋
- 與具體技術(shù)無(wú)關(guān)
- 支持驗(yàn)證
- 方便人類的讀寫(xiě)
XML示例
為了更好的了解XML,下面我們提供一個(gè)簡(jiǎn)單的XML文件,內(nèi)容如下所示:
<?xml version="1.0" encoding="UTF-8" ?>
<!--
根元素為students
注意XML文件中有且僅有一個(gè)根元素
-->
<students>
<!--
子元素student
id屬性同樣可以作為student的子元素
為了演示方便爱谁,這里將其作為屬性
-->
<student id="123">
<!--
student有三個(gè)子元素
name、age、gender
-->
<name>xuhuanfeng</name>
<age>22</age>
<gender>male</gender>
</student>
<!--同上-->
<student id="456">
<name>Tom</name>
<age>23</age>
<gender>femal</gender>
</student>
<!--同上-->
<student id="789">
<name>Lily</name>
<age>24</age>
<gender>femal</gender>
</student>
</students>
在XML中每個(gè)元素都可以有子元素/值锯七,元素可以有屬性,具體關(guān)于XML的內(nèi)容還請(qǐng)查看官方的文檔誉己,接下來(lái)的內(nèi)容主要為Java對(duì)XML文件的解析眉尸。
XML解析
XML解析主要有兩種方式,一種稱為DOM解析巫延,另外一種稱之為SAX解析效五。
- DOM解析:Document Object Model,簡(jiǎn)單的來(lái)講炉峰,DOM解析就是讀取XML文件畏妖,然后在文件文檔描述的內(nèi)容在內(nèi)存中生成整個(gè)文檔樹(shù)。
- SAX解析:Simple API for XML疼阔,簡(jiǎn)單的來(lái)講戒劫,SAX是基于事件驅(qū)動(dòng)的流式解析模式半夷,一邊去讀文件,一邊解析文件迅细,在解析的過(guò)程并不保存具體的文件內(nèi)容巫橄。
兩種解析方式各有千秋,也都有各自的有點(diǎn)和缺點(diǎn)茵典,這里簡(jiǎn)單羅列如下:
-
DOM解析:
- 優(yōu)點(diǎn):在內(nèi)存中形成了整個(gè)文檔樹(shù)湘换,有了文檔樹(shù),就可以隨便對(duì)文檔中任意的節(jié)點(diǎn)進(jìn)行操作(增加節(jié)點(diǎn)统阿、刪除節(jié)點(diǎn)彩倚、修改節(jié)點(diǎn)信息等),而且由于已經(jīng)有了整個(gè)的文檔樹(shù)扶平,可以實(shí)現(xiàn)對(duì)任意節(jié)點(diǎn)的隨機(jī)訪問(wèn)帆离。
- 缺點(diǎn):由于需要在內(nèi)存中形成文檔樹(shù),需要消耗的內(nèi)存比較大结澄,尤其是當(dāng)文件比較大的時(shí)候哥谷,消耗的代價(jià)還是不容小視的。
-
SAX解析:
- 優(yōu)點(diǎn):SAX解析由于是一邊讀取文檔一邊解析的麻献,所以所占用的內(nèi)存相對(duì)來(lái)說(shuō)比較小们妥。
- 缺點(diǎn):無(wú)法保存文檔的信息,無(wú)法實(shí)現(xiàn)隨機(jī)訪問(wèn)節(jié)點(diǎn)勉吻,當(dāng)文檔需要編輯的時(shí)候蒜危,使用SAX解析就比較麻煩了要糊。
對(duì)XML的兩種不同解析機(jī)制有一定的了解之后祭隔,接下來(lái)我們就來(lái)具體的看下德频,在Java中是如何解析的。
DOM解析
關(guān)于DOM的解析源譬,這里就不再做過(guò)多的解釋了集惋,直接通過(guò)代碼來(lái)查看具體的操作過(guò)程
解析文檔
public void parse() {
// students的內(nèi)容為上面所示XML代碼內(nèi)容
File file = new File("D:/students.xml");
try {
// 創(chuàng)建文檔解析的對(duì)象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析文檔,形成文檔樹(shù)踩娘,也就是生成Document對(duì)象
Document document = builder.parse(file);
// 獲得根節(jié)點(diǎn)
Element rootElement = document.getDocumentElement();
System.out.printf("Root Element: %s\n", rootElement.getNodeName());
// 獲得根節(jié)點(diǎn)下的所有子節(jié)點(diǎn)
NodeList students = rootElement.getChildNodes();
for (int i = 0; i < students.getLength(); i++){
// 獲得第i個(gè)子節(jié)點(diǎn)
Node childNode = students.item(i);
// 由于節(jié)點(diǎn)多種類型刮刑,而一般我們需要處理的是元素節(jié)點(diǎn)
// 元素節(jié)點(diǎn)就是非空的子節(jié)點(diǎn),也就是還有孩子的子節(jié)點(diǎn)
if (childNode.getNodeType() == Node.ELEMENT_NODE){
Element childElement = (Element)childNode;
System.out.printf(" Element: %s\n", childElement.getNodeName());
System.out.printf(" Attribute: id = %s\n", childElement.getAttribute("id"));
// 獲得第二級(jí)子元素
NodeList childNodes = childElement.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++){
Node child = childNodes.item(j);
if (child.getNodeType() == Node.ELEMENT_NODE){
Element eChild = (Element) child;
System.out.printf(" sub Element: %s value= %s\n", eChild.getNodeName(), eChild.getTextContent());
}
}
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
解析的結(jié)果如下所示:
Root Element: students
Element: student
Attribute: id = 123
sub Element: name value= xuhuanfeng
sub Element: age value= 22
sub Element: gender value= male
# 其余兩個(gè)student節(jié)點(diǎn)由于篇幅原因這里省略...
當(dāng)我們需要特定的節(jié)點(diǎn)的數(shù)據(jù)的時(shí)候养渴,可以根據(jù)具體的數(shù)據(jù)從上面的解析過(guò)程中進(jìn)行數(shù)據(jù)的篩選即可雷绢,所以這里不演示如果進(jìn)行數(shù)據(jù)的選取了(畢竟整個(gè)文檔的內(nèi)容都讀取出來(lái)了:))
編輯文檔
由于DOM解析是直接在內(nèi)存中生成對(duì)應(yīng)的文檔樹(shù),所以我們可以很方便地對(duì)其進(jìn)行編輯理卑,這里演示修改id = 123的子元素name的值為Huanfeng.Xu翘紊,具體代碼如下所示:
public void modify(){
try {
// 生成文檔樹(shù)的過(guò)程同前面所示,這里不進(jìn)行過(guò)多的解釋
File file = new File("d:/students.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(file);
Element rootElement = document.getDocumentElement();
NodeList students = rootElement.getChildNodes();
for (int i = 0; i < students.getLength(); i++){
Node tmp = students.item(i);
if (tmp.getNodeType() == Node.ELEMENT_NODE){
Element element = (Element)tmp;
// 獲得id為123的student節(jié)點(diǎn)
String attr = element.getAttribute("id");
if ("123".equalsIgnoreCase(attr)){
NodeList childNodes = element.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++){
Node childNode = childNodes.item(j);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
Element childElement = (Element) childNode;
// 修改子節(jié)點(diǎn)name的值
if (childElement.getNodeName().equalsIgnoreCase("name")) {
childElement.setTextContent("Huanfeng.Xu");
break;
}
}
}
}
}
}
// 獲得Transformer對(duì)象藐唠,用于輸出文檔
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
// 封裝成DOMResource對(duì)象
DOMSource domSource = new DOMSource(document);
Result result = new StreamResult("d:/newStudents.xml");
// 輸出結(jié)果
transformer.transform(domSource, result);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
}
可以看到帆疟,基本的操作跟解析文檔是一致的鹉究,這也非常好理解,修改嘛踪宠,肯定先要解析文檔然后獲得需要修改的節(jié)點(diǎn)信息自赔,這里同樣可以對(duì)節(jié)點(diǎn)進(jìn)行刪除、增加操作柳琢,原理同上绍妨,這里就不進(jìn)行演示。
SAX解析
關(guān)于SAX解析的原理柬脸,這里就不再做過(guò)多的解釋痘绎,同上面DOM的解析一樣,這里我們直接通過(guò)代碼來(lái)查看具體的操作過(guò)程
解析文檔
/**
* 由于SAX解析是基于事件機(jī)制的肖粮,也就是當(dāng)遇到指定元素的時(shí)候,解析器就會(huì)自動(dòng)調(diào)用
* 回調(diào)函數(shù)尔苦,所以使用SAX解析的時(shí)候涩馆,需要?jiǎng)?chuàng)建自定義的Handler并且繼承自DefaultHandler
* 并且將其傳給解析器,用于指定需要進(jìn)行回調(diào)的內(nèi)容
*/
class SAXHandler extends DefaultHandler{
/**
* 用于標(biāo)志是否已經(jīng)讀取到指定的元素
*/
private boolean isName;
private boolean isAge;
private boolean isGender;
@Override
public void startDocument() throws SAXException {
System.out.println("Starting parse the document");
}
@Override
public void endDocument() throws SAXException {
System.out.println("Ending parse the document");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("student".equalsIgnoreCase(qName)){
System.out.println("student");
}else if ("name".equalsIgnoreCase(qName)){
isName = true;
}else if ("age".equalsIgnoreCase(qName)){
isAge = true;
}else if ("gender".equalsIgnoreCase(qName)){
isGender = true;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length);
if (isName){
System.out.printf(" Name: %s\n", content);
isName = false; // 這里需要額外注意允坚,當(dāng)讀取到一個(gè)節(jié)點(diǎn)之后魂那,需要
// 把該節(jié)點(diǎn)的標(biāo)志去除,不然下一次讀取會(huì)出現(xiàn)問(wèn)題
}else if (isAge){
System.out.printf(" Age: %s\n", content);
isAge = false;
}else if (isGender){
System.out.printf(" Gender: %s\n", content);
isGender = false;
}
}
}
public void parser(){
try {
File file = new File("d:/students.xml");
// 創(chuàng)建一個(gè)SAX解析器
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
javax.xml.parsers.SAXParser parser = saxParserFactory.newSAXParser();
// 解析對(duì)應(yīng)的文件
parser.parse(file, new SAXHandler());
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
對(duì)應(yīng)的輸出結(jié)果如下所示:
student
Name: xuhuanfeng
Age: 22
Gender: male
# 這里由于篇幅原因稠项,省略其他兩個(gè)輸出內(nèi)容
由于SAX解析本身不利于節(jié)點(diǎn)的保存以及編輯涯雅,所以這里就不演示器編輯的過(guò)程。
第三方類庫(kù)解析
上面的內(nèi)容就是XML解析的最基本的操作了展运,不過(guò)活逆,由于原生API操作不方便,加上效率不怎么高拗胜,所以就出現(xiàn)了許多的第三方的解析類庫(kù)蔗候,最常使用的包括了JDOM、StAX埂软、XPath锈遥、DOM4j等,下面我們將逐個(gè)演示其操作
JDOM解析
JDOM是我們所要接觸的第一個(gè)第三方解析類庫(kù)勘畔,其操作的原理是基于DOM解析操作所灸,不過(guò)JDOM的解析效率比原生操作高,內(nèi)存占用相對(duì)低炫七,使用的時(shí)候需要導(dǎo)入JDOM的jar文件爬立,下載地址
解析文檔
public void parse(){
try {
File file = new File("d:/students.xml");
// 獲得一個(gè)解析器
SAXBuilder saxBuilder = new SAXBuilder();
Document document = saxBuilder.build(file);
// 獲得根元素
Element rootElement = document.getRootElement();
System.out.printf("Root Element %s\n", rootElement.getName());
List<Element> elements = rootElement.getChildren();
for (Element e : elements){
System.out.printf(" %s\n", e.getName());
System.out.printf(" Name: %s\n", e.getChild("name").getTextTrim());
System.out.printf(" Age: %s\n", e.getChild("age").getTextTrim());
System.out.printf(" Gender: %s\n", e.getChild("gender").getTextTrim());
}
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到使用JDOM進(jìn)行解析是比較方便的,而且由于JDOM使用了List等容器類万哪,更加方便操作了懦尝。
StAX解析
StAx是我們要使用的第二個(gè)第三方解析類庫(kù)知纷,StAX的實(shí)現(xiàn)原理為SAX操作,不過(guò)StAX提供了比原生SAX解析更加方便的操作陵霉,使用時(shí)同樣需要導(dǎo)入其jar文件琅轧,下載地址
解析文檔
public void parse(){
boolean isName = false;
boolean isAge = false;
boolean isGender = false;
try {
File file = new File("d:/students.xml");
// 獲得解析器
XMLInputFactory factory = XMLInputFactory.newFactory();
XMLEventReader reader = factory.createXMLEventReader(new FileReader(file));
while (reader.hasNext()){
// 獲得事件
XMLEvent event = reader.nextEvent();
switch (event.getEventType()){
// 解析事件的類型
case XMLStreamConstants.START_ELEMENT:
StartElement startElement = event.asStartElement();
String qName = startElement.getName().getLocalPart();
if ("name".equalsIgnoreCase(qName)){
isName = true;
}else if ("age".equalsIgnoreCase(qName)){
isAge = true;
}else if ("gender".equalsIgnoreCase(qName)){
isGender = true;
}
break;
case XMLStreamConstants.CHARACTERS:
Characters characters = event.asCharacters();
if (isName){
System.out.printf(" Name: %s\n", characters.getData());
isName = false;
}else if (isAge){
System.out.printf(" Age: %s\n", characters.getData());
isAge = false;
}else if (isGender){
System.out.printf(" Gender: %s\n", characters.getData());
isGender = false;
}
break;
}
}
} catch (XMLStreamException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
XPath
XPath從嚴(yán)格意義上來(lái)講并不是一種解析方式,不過(guò)XPath提供了一種定位節(jié)點(diǎn)的方式踊挠,XPath表達(dá)式乍桂,通過(guò)該表達(dá)式,我們可以定位到指定特性的一個(gè)或者一組節(jié)點(diǎn)
常用的XPath表達(dá)式如下所示:
/ :從根節(jié)點(diǎn)開(kāi)始查找
//:從當(dāng)前節(jié)點(diǎn)開(kāi)始查找
. :選擇當(dāng)前節(jié)點(diǎn)
..:選擇當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)
@:指定元素
還有其他一些表達(dá)式效床,可以參考XPath表達(dá)式
解析文檔
public void parse(){
try {
File file = new File("d:/students.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 創(chuàng)建xpath對(duì)象
XPath xPath = XPathFactory.newInstance().newXPath();
Document document = builder.parse(file);
// 編寫(xiě)xpath表達(dá)式
String expression = "/students/student";
NodeList students = (NodeList)xPath.compile(expression).evaluate(document, XPathConstants.NODESET);
for (int i = 0; i < students.getLength(); i++){
Node node = students.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE){
Element element = (Element) node;
System.out.printf(" Element: %s\n", element.getNodeName());
System.out.printf(" Name: %s\n", element.getElementsByTagName("name").item(0).getTextContent());
System.out.printf(" Age: %s\n", element.getElementsByTagName("age").item(0).getTextContent());
System.out.printf(" Gender: %s\n", element.getElementsByTagName("gender").item(0).getTextContent());
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
}
}
可以看到睹酌,使用XPath技術(shù)本質(zhì)上還是使用DOM解析,只不過(guò)借助XPath表達(dá)式剩檀,可以很方便地定位到指定元素
DOM4J解析
DOM4J是一個(gè)比較優(yōu)秀的解析類庫(kù)憋沿,也是目前使用得比較多的庫(kù)類,使用的時(shí)候可以配合XPath技術(shù)來(lái)輔助定位某一個(gè)節(jié)點(diǎn)沪猴,使用的時(shí)候需要導(dǎo)入對(duì)應(yīng)的jar文件辐啄,下載地址,注意使用DOM4J的時(shí)候需要導(dǎo)入兩個(gè)jar文件运嗜,DOM4J本身的jar文件以及jaxen文件
解析文檔
public void parse() throws DocumentException {
File file = new File("d:/students.xml");
// 加載文檔
SAXReader reader = new SAXReader();
Document document = reader.read(file);
Element rootElement = document.getRootElement();
System.out.printf("Root Element: %s\n", rootElement.getName());
// 使用XPath表達(dá)式來(lái)定位節(jié)點(diǎn)
List<Node> students = document.selectNodes("/students/student");
for (Node n: students){
System.out.printf("Element: %s\n", n.getName());
System.out.printf("Name: %s\n", n.selectSingleNode("name").getText());
System.out.printf("Age: %s\n", n.selectSingleNode("age").getText());
System.out.printf("Gender: %s\n", n.selectSingleNode("gender").getText());
}
}
可以看到壶辜,使用DOM4J解析文檔是非常方便的,不僅如此担租,使用DOM4J生成文檔也是非常方便的
生成文檔
public void create() throws IOException {
Document document = DocumentHelper.createDocument();
Element root = document.addElement("students");
Element student = root.addElement("student");
student.addElement("name")
.addText("xuhuanfeng");
student.addElement("age")
.addText("22");
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(System.out);
writer.write(document);
}
總結(jié)
本節(jié)我們學(xué)習(xí)了XML解析的機(jī)制砸民,包括了DOM解析以及SAX解析,并且通過(guò)具體實(shí)例使用不同解析技術(shù)進(jìn)行解析奋救,還了解了幾個(gè)常用的XML解析類庫(kù)岭参,包括了JDOM、StAX尝艘、XPath冗荸、DOM4J等,并且通過(guò)具體操作更加具體地了解了其操作的過(guò)程利耍。