XML简介
可扩展标记语言,简称XML(Extensible Markup Language),是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。
—–维基百科
XML是一种树结构,从根部开始,扩展到枝叶。
book.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<bookstore>
<book id = "1">
<name>Thinking in java</name>
<author>Bruce Eckel</author>
<year>2014</year>
</book>
<book id = "2" author = "liu">
<name>爱的供养</name>
<year>2004</year>
</book>
</bookstore>
第一行是XML声明,它定义XML的版本和字符编码
<bookstore>是根元素,它下面有三个子元素,</bookstore>是根元素的结尾也就是闭合标签,每个元素都必须要有相应的闭合标签,这不同于HTML。XML文档必须包含根元素,所有的元素都可以有子元素,只有你想。所有的元素可拥有文本内容和属性。同时,在XML中,标签是区分大小写的。
Java对XML文件的读取
Java对XML文件的操作一共有四种方式,分别是:
DOM
SAX
JDOM
DOM4J
其中1和2是Java官方提供的对XML操作的方式。下面我们分别来谈谈这四种解析方式。
DOM
DOM是官方提供的与平台无关的解析方式,它解析XML文件的方式是一次性把XML文件全部读取进内存,这样做的缺点就是当文件非常大时,耗内存。
优点:
1.形成了树结构,直观好理解,代码容易编写。
2.解析过程中树结构保存在内存中,方便修改。
缺点:
一次性把文件全部读取进内存,当文件大到一定程度时,耗费内存,且可能会造成内存溢出。
下面来看看DOM读取XML文件的代码
package com.codeliu.dom;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @author liu
* @version 创建时间:2018年3月24日 下午7:32:02
* 使用DOM读取XML文件的内容 DOMTest.java
*/
public class DOMTest {
public static void main(String[] args) {
// 创建一个DocumentBuilderFactory对象
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
// 创建一个DocumentBuilder对象
DocumentBuilder db = dbf.newDocumentBuilder();
// 使用parse方法解析xml文件
Document document = db.parse("book.xml");
NodeList nl = document.getElementsByTagName("book");
System.out.println("文件中包含有" + nl.getLength() + "本书");
for(int i = 0; i < nl.getLength(); i++) {
// 通过item(i)获取一个book节点
Node book = nl.item(i);
// 获取一个book节点的所有属性
NamedNodeMap nnm = book.getAttributes();
for(int j = 0; j < nnm.getLength(); j++) {
System.out.println(nnm.item(j).getNodeName() + ":" + nnm.item(j).getNodeValue());
}
NodeList childNodes = book.getChildNodes();
// 子节点包括空格换行在内
System.out.println("一个book节点下共有" + childNodes.getLength() + "个子节点");
for(int m = 0; m < childNodes.getLength(); m++) {
// 如果子节点是element类型的,就输出
if(childNodes.item(m).getNodeType() == Node.ELEMENT_NODE) {
System.out.println(childNodes.item(m).getNodeName() + ":" + childNodes.item(m).getFirstChild().getNodeValue());
}
}
}
}
catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
SAX
SAX是官方提供的基于事件驱动的解析方式。所谓基于事件驱动的解析,就是在解析的过程中,是一步一步进行解析的,读到一个标签,就进行解析,不想DOM一样,一次性把整个文件全部读入内存。在后面的代码中我们会看到,使用SAX方式解析XML文件时,我们要写最近的Handler类继承DefaultHandler类,并重写startElement,endElement,startDocument,endDocument,以及characters方法,这样才能进行解析。
优点:
1.采用事件驱动,对内存耗费比较小。
2.适用于只需要处理XML文件中的数据,而不关心XML结构的场景。
缺点:
1.不易编码,要继承DefaultHandler类并重写方法。
2.很难同时访问同一个XML文件中不同地方的数据。
下面来看看SAX解析XML文件的代码(因为要继承,所以写了3个类)
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
/**
* @author liu
* @version 创建时间:2018年3月24日 下午8:26:02
* 使用SAX来解析XML文件 SAXTest.java
*/
public class SAXTest {
public static void main(String[] args) {
// 创建一个SAXParserFactory对象
SAXParserFactory spf = SAXParserFactory.newInstance();
try {
// 创建一个SAXParser对象
SAXParser parser = spf.newSAXParser();
// 传入自定义的handler对象
MyHandler mh = new MyHandler();
parser.parse("book.xml", mh);
for(Book book:mh.getBookList()) {
System.out.println(book);
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @author liu
* @version 创建时间:2018年3月24日 下午9:17:38
* 读取每一本书的信息并进行保存 Book.java
*/
public class Book {
private String id;
private String name;
private String author;
private String year;
/**
* @return id
*/
public String getId() {
return id;
}
/**
* @param id 要设置的 id
*/
public void setId(String id) {
this.id = id;
}
/**
* @return name
*/
public String getName() {
return name;
}
/**
* @param name 要设置的 name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return author
*/
public String getAuthor() {
return author;
}
/**
* @param author 要设置的 author
*/
public void setAuthor(String author) {
this.author = author;
}
/**
* @return year
*/
public String getYear() {
return year;
}
/**
* @param year 要设置的 year
*/
public void setYear(String year) {
this.year = year;
}
@Override
public String toString() {
return "Book [id=" + id + ", name=" + name + ", author=" + author + ", year=" + year + "]";
}
}
package com.codeliu.SAX;
import java.util.ArrayList;
import javax.xml.stream.events.StartElement;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* @author liu
* @version 创建时间:2018年3月24日 下午8:32:18
* 继承DefaultHandler类 MyHandler.java
*/
public class MyHandler extends DefaultHandler {
String value = " ";
Book book = null;
private ArrayList<Book> bookList = new ArrayList<Book>();
/**
* @return bookList
*/
public ArrayList<Book> getBookList() {
return bookList;
}
/**
* 用来标志解析开始
*/
@Override
public void startDocument() throws SAXException {
super.startDocument();
System.out.println("start");
}
/**
* 用来标志解析结束
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
System.out.println("end");
}
/**
* 用来遍历xml文件的开始标签
*/
@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 = new Book();
System.out.println("遍历一本书开始");
int num = attributes.getLength();
for(int i = 0; i < num; i++) {
if(attributes.getQName(i).equals("id")) {
book.setId(attributes.getValue(i));
} else if(attributes.getQName(i).equals("author")) {
book.setAuthor(attributes.getValue(i));
}
}
}
}
/**
* 获取标签中的内容
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
value = new String(ch, start, length);
}
/**
* 用来遍历xml文件的结束标签
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if(qName.equals("book")) {
bookList.add(book);
book = null;
System.out.println("遍历一本书结束");
} else if(qName.equals("name")) {
book.setName(value);
} else if(qName.equals("author")) {
book.setAuthor(value);
} else if(qName.equals("year")) {
book.setYear(value);
}
}
}
JDOM
JDOM不是官方提供的解析方式,所以如果我们要使用的话,得先把jar包导入,下面是下载地址http://www.jdom.org/downloads/index.html
如何在项目中导入jar包,相信大家都很熟悉,这里就不赘述,我建议大家把jar包导入的同时,把JDOM的源码一起导入,这样查看源码也方便。
1.JDOM仅使用了具体类而没有使用接口
2.JDOM的API大量使用了Collections类
下面我们来看看JDOM解析XML的方式(Book.java在上面已经贴出来了)
package com.codeliu.JDOM;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
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;
/**
* @author liu
* @version 创建时间:2018年3月25日 上午8:34:47
* 使用JDOM解析xml文件 JDOMTest.java
*/
public class JDOMTest {
public static void main(String[] args) {
SAXBuilder sb = new SAXBuilder();
// 遍历书,结果存储在books中
ArrayList<Book> books = new ArrayList<Book>();
FileInputStream in;
try {
// 创建一个输入流,将xml文件加载到输入流中
in = new FileInputStream("book.xml");
// 解析中文时出现乱码,可以改变编码格式
//InputStreamReader isr = new InputStreamReader(in, "utf-8");
// 通过build方法,将输入流加载到SAXBuilder中
Document build = sb.build(in);
// 获取根节点
Element root = build.getRootElement();
// 获取根节点下的子节点
List<Element> childList = root.getChildren();
// 获取子节点的数量
System.out.println(childList.size());
int count = 1;
for(Element child:childList) {
Book book = new Book();
System.out.println("--------------开始解析第" + count + "本书----------------");
// 获取属性列表
List<Attribute> attr = child.getAttributes();
for(Attribute a:attr) {
// 输出子节点的属性名和属性值
System.out.println(a.getName() + ":" + a.getValue());
if("id".equals(a.getName())) {
book.setId(a.getValue());
} else if("author".equals(a.getName())) {
book.setAuthor(a.getValue());
}
}
// 获取子节点下面的子节点
List<Element> childrens = child.getChildren();
for(Element children:childrens) {
// 输出子节点的子节点的属性名和属性值
System.out.println(children.getName() + ":" + children.getText());
if("name".equals(children.getName())) {
book.setName(children.getValue());
} else if("year".equals(children.getName())) {
book.setYear(children.getValue());
} else if("author".equals(children.getName())) {
book.setAuthor(children.getValue());
}
}
System.out.println("------------------第" + count + "本书解析完毕----------");
count++;
books.add(book);
book = null;
}
for(Book book:books) {
System.out.println(book);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
DOM4J
jar包下载地址https://github.com/dom4j/dom4j/releases/tag/dom4j_1_6_1
1.DOM4J是JDOM的一种智能分支,它合并了许多超出基本xml文档表示的功能
2.DOM4J使用接口和抽象基本类方法,是一个优秀的Java XML API
3.具有性能优异,灵活性好,功能强大和易使用的特点
4.源代码开放
下面看看DOM4J解析XML文件的代码
package com.codeliu.DOM4J;
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;
/**
* @author liu
* @version 创建时间:2018年3月26日 上午11:22:29
* 使用DOM4J解析xml文件
*/
public class DOM4JTest {
public static void main(String[] args) {
SAXReader reader = new SAXReader();
try {
Document read = reader.read("book.xml");
// 获取根节点
Element root = read.getRootElement();
// 通过elementIterator方法获取迭代器
Iterator books = root.elementIterator();
// 遍历迭代器
while(books.hasNext()) {
System.out.println("------------开始遍历一本书------------");
Element b = (Element)books.next();
// 获取每本书的属性
List<Attribute> bookList = b.attributes();
for(Attribute a:bookList) {
System.out.println(a.getName() + ":" + a.getValue());
}
// 获取每本书下面的子节点
Iterator childBook = b.elementIterator();
while(childBook.hasNext()) {
Element c = (Element)childBook.next();
System.out.println(c.getName() + ":" + c.getStringValue());
}
System.out.println("-------------遍历完成一本书------------");
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
四种解析方式的对比
我们可以把上面四种方式的解析代码整合到一个测试类里,使用System.currentTimeMillis()函数就可以对比解析同一个Book.xml各自的用时。我测试的结果如下:
DOM:461
SAX:21
JDOM:264
DOM4J:185
本文用来测试的Book.xml文件非常小,有兴趣的读者可以试试用几兆或者几十兆的xml文件进行测试,结果又会不一样。现在DOM4J使用的比较广泛,其他三种也各有各的优势,具体用什么方式,要视情况而定。
结束语
写了快两个小时,差不多又写了近1w字了,学习Java任重道远呐,一起加油吧。