文章目录
一.什么是XML?
XML是可扩展标记语言(eXtensible Markup Language)
的缩写,它是是一种数据表示格式
,可以描述非常复杂的数据结构,常用于传输和存储数据
例如,一个表示书籍
的XML对象可能如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
二.XML特点
纯文本
,默认使用UTF-8编码
,- 可嵌套,适合表示
结构化数据
。如果把XML内容存为文件,那么它就是一个XML文件,例如:book.xml
XML经常通过网络作为消息传输
三.XML结构
1.基本结构
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
XML固定的结构
-
首行必定是
<?xml version="1.0" encoding="UTF-8" ?>,声明文档的版本号以及编码格式
-
第二行声明的是
<!DOCTYPE note SYSTEM "book.dtd">
,声明是对外部 DTD 文件的引用,定义当前文档结构
DTD 的目的是定义 XML 文档的结构。它使用一系列合法的元素来定义文档结构:
<!DOCTYPE book [ <!ELEMENT book(name,author,isbn tags,pubDate)> <!ELEMENT name(#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT isbn (#PCDATA)> <!ELEMENT tags(#PCDATA)> <!ELEMENT pubDate(#PCDATA)> ]>
-
XML的文档内容
- 一个XML文档有且仅有一个
根元素
- 根元素可以包含
任意个子元素
,元素可以包含属性,例如:<isbn lang="CN">1234567</isbn>
<isbn>
包含一个属性lang="CN"
, - 元素必须正确嵌套。
- 如果是空元素,可以用
<tag/>
表示。
- 一个XML文档有且仅有一个
-
所有的 XML 元素都必须有一个关闭标签,在 XML 中,省略关闭标签是非法的。
-
XML 标签对大小写敏感
-
XML 属性值必须加引号
2.特殊字符
由于使用了<、>
以及引号等标识符为xml文档关键字,如果内容出现了改符号, 必须进行转义
。
例如,Java<tm>
必须写成
<name>Java<tm></name>
常见的特殊字符如下:
字符 | 表示 |
---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
3.验证XML文件的正确性
- 格式正确的XML(Well Formed)是指 : XML的格式是正确的,
可以被解析器正常读取
。 - 合法的XML是指: 不但XML格式正确,而且它的
数据结构可以被DTD或者XSD验证
。
DTD文档可以指定一系列规则
,例如:
根元素
必须是book- book元素必须包含name,author等指定元素
- isbn元素必须包含属性lang
- …
如何验证XML文件的正确性呢?
- 最简单的方式是通过浏览器验证。 可以直接把XML文件拖拽到浏览器窗口,
如果格式错误,浏览器会报错。
- 和结构类似的HTML不同,
浏览器对HTML有一定的“容错性”
,缺少结束标签也可以被解析
,但XML要求严格的格式,任何没有正确嵌套的标签都会导致错误。
XML是一个技术体系,除了我们经常用到的XML文档本身外,XML还支持:
DTD和XSD
:验证XML结构和数据是否有效;Namespace
:XML节点和属性的名字空间;XSLT
:把XML转化为另一种文本;XPath
:一种XML节点查询语言;- …
实际上,XML的这些相关技术实现起来非常复杂,在实际应用中很少用到,通常了解一下就可以了。
4.XML标签命名规则
- 名称可以包含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字母 xml(或者 XML、Xml 等等)开始
- 名称不能包含空格
最佳命名习惯
-
推荐使用下划线进行命名:<first_name>、<last_name>。
-
名称应简短和简单,比如:<book_title>,而不是:<the_title_of_the_book>。
-
避免 “-” 字符。如:“first-name”,一些软件会认为您想要从 first 里边减去 name。
-
避免 “.” 字符。如:“first.name”,一些软件会认为 “name” 是对象 “first” 的属性。
-
避免 “:” 字符。冒号会被转换为命名空间来使用
5.XML小结
-
XML使用嵌套结构的数据表示方式,支持格式验证;
-
XML常用于
配置文件
、网络消息传输
等。
四.Java解析XML
XML是一种树形结构
的文档,它有两种标准的解析API:
DOM
:一次性
读取XML,并在内存
中表示为树形结构
SAX
:以流的
形式读取XML,使用事件回调
。
1.解析DOM
1.1什么是DOM?
DOM是Document Object Model
的缩写,DOM模型就是把XML结构
作为一个树形结构
处理,从根节点
开始,每个节点都可以包含任意个子节点。
以下面的XML为例:
<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
如果解析为DOM结构,它大概为这样
- 最顶层的
document
代表XML文档
它是真正的“根”
,而<book>
虽然是根元素,但它是document的一个子节点
。
1.2.Java以DOM方式解析XML?
Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:
Document
:代表整个XML文档
;Element
:代表一个XML元素
;Attribute
:代表一个元素的某个属性
。
示例代码
public class TestXml {
public static void printNode(Node n, int indent) {
for (int i = 0; i < indent; i++) {
System.out.print(' ');
}
switch (n.getNodeType()) {
case Node.DOCUMENT_NODE: // Document节点
System.out.println("Document: " + n.getNodeName());
break;
case Node.ELEMENT_NODE: // 元素节点
System.out.println("Element: " + n.getNodeName());
break;
case Node.TEXT_NODE: // 文本
System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.ATTRIBUTE_NODE: // 属性
System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
break;
default: // 其他
System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
}
for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
printNode(child, indent + 1);
}
}
@Test
public void testDom1() throws ParserConfigurationException, IOException, SAXException {
InputStream input = TestXml.class.getResourceAsStream("/book.xml");
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(input);
NodeList nodeList = document.getElementsByTagName("book");
for (int i = 0; i < nodeList.getLength(); i++) {
printNode(nodeList.item(i), i);
}
}
}
DocumentBuilder.parse()
用于解析一个XML,它可以接收InputStream,File或者UR
L,如果解析无误将返回一个Document对象
,这个对象代表了整个XML文档的树形结构
, 需要遍历以便读取指定元素的值
-
对于DOM API解析出来的结构,我们从
根节点Document
出发,可以遍历所有子节点
,获取所有元素
、属性
、文本数据
,还可以包括注释
,这些节点被统称为Node,每个Node都有自己的类型Type
,根据Type来区分一个Node到底是元素,还是属性,还是文本 -
使用DOM API时,如果要读取某个元素的文本,需要访问
Type=Text类型的子节点
,所以使用起来还是比较繁琐的。
1.3.实例代码
public class TestDom {
@Test
public void testDom() throws ParserConfigurationException, IOException, SAXException {
//1.创建DocumentBuilderFactory对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/main/resources/book.xml");
NodeList nodeList = document.getElementsByTagName("book");
element(nodeList);
// node(nodeList);
} catch (Exception e) {
e.printStackTrace();
}
}
//用Element方式
public static void element(NodeList list){
for (int i = 0; i <list.getLength() ; i++) {
Element element = (Element) list.item(i);
NodeList childNodes = element.getChildNodes();
for (int j = 0; j <childNodes.getLength() ; j++) {
if (childNodes.item(j).getNodeType()== Node.ELEMENT_NODE) {
//获取节点
System.out.print(childNodes.item(j).getNodeName() + ":");
//获取节点值
System.out.println(childNodes.item(j).getFirstChild() == null ? "null" : childNodes.item(j).getFirstChild().getNodeValue());
}
}
}
}
public static void node(NodeList list){
for (int i = 0; i <list.getLength() ; i++) {
Node node = list.item(i);
NodeList childNodes = node.getChildNodes();
for (int j = 0; j <childNodes.getLength() ; j++) {
if ( childNodes.item(j).getNodeType()==Node.ELEMENT_NODE) {
System.out.print(childNodes.item(j).getNodeName() + ":");
System.out.println(childNodes.item(j).getFirstChild() == null ? "null" : childNodes.item(j).getFirstChild().getNodeValue());
}
}
}
}
}
1.4.DOM解析小结
-
Java提供的DOM API可以将XML解析为DOM结构,以Document对象表示;
-
DOM可在内存中
完整表示XML数据结构
; -
DOM解析速度慢,内存占用大。
2.解析SAX
以下面的XML为例:
<?xml version="1.0" encoding="UTF-8" ?>
<class>
<student>
<firstname>cxx1</firstname>
<lastname>Bob1</lastname>
<nickname>stars1</nickname>
<marks>85</marks>
</student>
<student rollno="493">
<firstname>cxx2</firstname>
<lastname>Bob2</lastname>
<nickname>stars2</nickname>
<marks>85</marks>
</student>
<student rollno="593">
<firstname>cxx3</firstname>
<lastname>Bob3</lastname>
<nickname>stars3</nickname>
<marks>85</marks>
</student>
</class>
2.1.是什么是SAX解析
-
使用DOM解析XML的优点是用起来省事,但它的主要缺点是内存占用太大。
-
SAX是一种使用
事件回调机制
的XML解析器,事件由解析器
产生并通过回调函数
发送给应用程序,这种模式称为“推模式”
。 -
SAX
是Simple API for XML
的缩写,它是一种基于流
的解析方式,边读取XML边解析
,并以事件回调的方式让调用者获取数据。
因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。
SAX解析会触发一系列事件:
事件 | 描述 |
---|---|
startDocument | 开始读取XML文档 |
startElement | 读取到了一个元素,例如<book> |
characters | 读取到了字符 |
endElement | 读取到了一个结束的元素,例如</book> |
endDocument | 读取XML文档结束 |
public class TestSax {
@Test
public void testSax() throws ParserConfigurationException, SAXException, IOException {
InputStream input = TestSax.class.getResourceAsStream("/book.xml");
//1.获取SAXParserFactory实例
SAXParserFactory spf = SAXParserFactory.newInstance();
//2. 获取SAXparser实例
SAXParser saxParser = spf.newSAXParser();
//3.创建Handel对象并交给解析器
saxParser.parse(input, new MyHandler());
}
}
class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
print("start document");
}
@Override
public void endDocument() throws SAXException {
print("end document");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
print("start element:", localName, qName);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
print("end element:", localName, qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
print("characters:", new String(ch, start, length));
}
@Override
public void error(SAXParseException e) throws SAXException {
print("error:", e);
}
void print(Object... objs) {
for (Object obj : objs) {
System.out.print(obj);
System.out.print(" ");
}
System.out.println();
}
}
执行结果:
如果要读取<name>
节点的文本,我们就必须在解析过程中根据startElement()
和endElement()
定位当前正在读取的节点,可以使用栈结构保存
,每遇到一个startElement()入栈,每遇到一个endElement()出栈,读到characters()时我们才知道当前读取的文本是哪个节点的。 可见使用SAX API仍然比较麻烦。
2.2.SAX小结
-
SAX是一种
流式解析XML的API
; -
SAX通过
事件触发
,读取速度快
,消耗内存少
; -
调用方必须通过
回调方法
获得解析过程中的数据。
3.解析StAX
以下面的XML为例:
<?xml version="1.0" encoding="UTF-8" ?>
<class>
<student>
<firstname>cxx1</firstname>
<lastname>Bob1</lastname>
<nickname>stars1</nickname>
<marks>85</marks>
</student>
<student rollno="493">
<firstname>cxx2</firstname>
<lastname>Bob2</lastname>
<nickname>stars2</nickname>
<marks>85</marks>
</student>
<student rollno="593">
<firstname>cxx3</firstname>
<lastname>Bob3</lastname>
<nickname>stars3</nickname>
<marks>85</marks>
</student>
</class>
3.1.什么是StAX?
StAX与SAX类似,也是基于流式解析XML
和事件触发的
模式,不过事件不同与SAX的回调通知方式,需要应用程序自行遍历判断事件类型,从中筛选出要获取的节点的信息,所有事件类型都在XMLStreamConstants中定义,
常见的有:
字典 | 事件 |
---|---|
XMLStreamConstants.START_ELEMENT | 解析开标签节点事件 |
XMLStreamConstants.END_ELEMENT | |
闭标签节点事件 | |
XMLStreamConstants.CHARACTERS | |
文本节点事件 |
StAX这种解析的策略也被成为“拉模式”
。
- StAX首先要获取XML文档流对象,然后创建解析器工厂对象(XMLInputFactory),根据工厂对象创建解析器对象(XMLStreamReader)
这里的解析器实际上就是一个迭代器
,根据迭代器可以顺序获取事件类型
,并根据事件类型去调用解析器的其他方法获取节点内容进行处理
public class TestStax {
@Test
public void testStax() throws Exception {
InputStream in = TestStax.class.getResourceAsStream("/student.xml");
XMLInputFactory factory = XMLInputFactory.newFactory();
XMLStreamReader parser = factory.createXMLStreamReader(in);
while (parser.hasNext()) {
int event = parser.next();
// 解析开标签
if (event == XMLStreamConstants.START_ELEMENT) {
System.out.println("解析标签元素: " + parser.getLocalName());
int attCount = parser.getAttributeCount();
System.out.println("该标签属性数量: " + attCount);
for (int i = 0; i < attCount; i++) {
System.out.println("属性名:" + parser.getAttributeLocalName(0));
System.out.println("属性值:" + parser.getAttributeValue(0));
}
System.out.println();
continue;
}
// 解析文本
if (event == XMLStreamConstants.CHARACTERS) {
System.out.println("文本内容: \"" + parser.getText() + "\"");
System.out.println();
continue;
}
// 解析闭标签
if (event == XMLStreamConstants.END_ELEMENT) {
System.out.println("解析标签元素结束: " + parser.getLocalName());
System.out.println();
}
}
}
}
执行结果
4.三种方式的比较
- DOM 的优点在于面向节点树编程比较简单,也比较好理解,在解析DOM时就已经完整加载了文档树,对节点的遍历和导航(包括父节点、子节点、兄弟节点)比较方便,也易于添加和删除节点,但是在文档内容比较大的时候,性能消耗比较大,处理效率较低,不过一般都用作配置文件,内容不多,因此忽略不计。
如果xml文件本身内容较多,而且在很多情况下只想解析某一个节点而不想加载全部节点浪费资源,这时可考虑使用流机制的解析器SAX和StAX,能够降低性能消耗,提高效率。
-
SAX 的缺点非常明显,没有加载完整的文档结构,对节点信息的获取和处理依赖回调函数,当处理逻辑涉及多个多层节点之间的关系时,回调函数的逻辑会非常复杂和难以维护;而且流处理方式只允许从上往下处理,不允许回溯已经处理过的节点,另外SAX也不支持修改XML。
-
StAX 具有跟SAX一样的流处理的缺点,StAX包括了两套处理XML文档的API:一种是基于指针的API,效率高但是抽象化程度低;另一种是基于事件迭代器的API,效率低但是抽象化程序高。开发者可以根据需求做平衡和选择。
5.使用Jackson
以以下XML为例
<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
对应Java对象
public class Book {
public long id;
public String name;
public String author;
public String isbn;
public List<String> tags;
public String pubDate;
}
Jackson的开源的第三方库可以轻松做到XML到JavaBean的转换。
我们要使用Jackson,先添加两个Maven的依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
public class TestJackson {
@Test
public void TestXMLToBean() throws IOException {
InputStream input = TestJackson.class.getResourceAsStream("/book.xml");
JacksonXmlModule module = new JacksonXmlModule();
XmlMapper mapper = new XmlMapper(module);
Book book = mapper.readValue(input, Book.class);
System.out.println(book.id);
System.out.println(book.name);
System.out.println(book.author);
System.out.println(book.isbn);
System.out.println(book.tags);
System.out.println(book.pubDate);
}
}
class Book {
public long id;
public String name;
public String author;
public String isbn;
public List<String> tags;
public String pubDate;
}
执行结果