九、XML
1、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内容经常通过网络作为消息传说。
1.1 XML的结构
xml有着固定的结构,第一行一定是 <?xml version="1.0" ?>
,可以加上可选的编码。
<!DOCTYPE note SYSTEM "book.dtd">
声明的是文档定义类型(DTD: Document Type Definition),是可选的。
下面的才是xml的文档内容。需要注意的是一个xml文档有且仅有一个根元素。
当内容中出现了特殊符号时,需要转义,因为xml文档中已经使用 <
、 >
、 ''
等做标识符。
字符 | 表示 |
---|---|
< | < |
> | > |
& | & |
" | " |
’ | ' |
例如内容为 Java<tm>
时应该写成:
<name>Java<tm></name>
2、解析XML
XML是一种树形结构的文档,它有着两种标准的解析API:
- DOM:一次性读取XML,并在内存中表示为树形结构;
- SAX:以流的形式读取XML,使用事件回调。
以下面的xml为例(book.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<book id="1" category="computer">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
2.1 使用DOM
book.xml
解析为DOM结构如下:
Java提供了DOM API来解析xml,使用了下面的对象来表示xml的内容:
-
Document:代表整个xml文档;
-
Element:代表一个xml元素;
xml元素指的是从
开始标签
到结束标签
的部分。一个元素可以包括:- 其他元素
- 文本
- 属性(下面的Attribute)
-
Attribute:代表一个元素的某个属性。
使用DOM API解析XML文档的代码如下:
public class XMLUtil {
public static void main(String args[]) throws IOException, SAXException, ParserConfigurationException {
parseXML();
}
public static void parseXML() throws ParserConfigurationException, IOException, SAXException {
InputStream input = XMLUtil.class.getResourceAsStream("/book.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);
printNode(doc, 0);
}
//遍历以读取指定元素的值:
static void printNode(Node n, int indent) {
for (int i = 0; i < indent; i++) {
System.out.print(' ');
}
switch (n.getNodeType()) {
case Node.DOCUMENT_NODE:
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;
case Node.CDATA_SECTION_NODE:
System.out.println("CDATA: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.COMMENT_NODE:
System.out.println("Comment: " + 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);
}
}
}
其中的
DocumentBuilder.parse()
用于解析一个xml,它可以接受InputStream、File、URL,会返回一个Document对象,这个对象代表了整个xml文档的属性结构
输出的机构如下:
//输出结果
Document: #document
Element: book
Text: #text =
Element: name
Text: #text = Java核心技术
Text: #text = //输出这个是因为在xml中,元素 `name` 和元素 `author` 存在换行,解析器把当成了text处理
Element: author
Text: #text = Cay S. Horstmann
Text: #text =
Element: isbn
Text: #text = 1234567
Text: #text =
Element: tags
Text: #text =
Element: tag
Text: #text = Java
Text: #text =
Element: tag
Text: #text = Network
Text: #text =
Text: #text =
Element: pubDate
Text: #text = 2020-03-13
Text: #text =
2.2 使用SAX
使用DOM解析的优点是简单省事,但它的主要缺点是如果文件过大,占用内存太大。
针对内存太大的问题,就有了另外一种解析xml的方法是SAX(Simple API for XML)。它是一种基于流的解析方法,边读取XML边解析,并以事件回调的方法让调用者获取数据。也正是因为一边读取一边解析,所以不论XML文件多大,占用的内存都很小。
SAX解析会触发一系列的事件:
startDocument
:开始读取XML文档;startElement
:读取到了一个元素,例如<book>
;characters
:读取到了字符;endElement
:读取到了一个结束的元素,例如</book>
;endDocument
:读取XML文档结束。
如果用SAX API解析XML,其Java代码如下:
public class XMLUtil {
public static void main(String args[]) throws Exception {
parseXML2();
}
public static void parseXML2() throws Exception {
InputStream input = XMLUtil.class.getResourceAsStream("/book.xml");
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(input, new MyHandler());
}
}
class MyHandler extends DefaultHandler {
public void startDocument() throws SAXException {
print("start document");
}
public void endDocument() throws SAXException {
print("end document");
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
print("start element:", localName, qName);
}
public void endElement(String uri, String localName, String qName) throws SAXException {
print("end element:", localName, qName);
}
public void characters(char[] ch, int start, int length) throws SAXException {
print("characters:", new String(ch, start, length));
}
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();
}
}
关于
startElement
中的参数是什么意思?/** * Receive notification of the start of an element. * * <p>By default, do nothing. Application writers may override this * method in a subclass to take specific actions at the start of * each element (such as allocating a new tree node or writing * output to a file).</p> * * @param uri The Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName The local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName The qualified name (with prefix), or the * empty string if qualified names are not available. * @param attributes The attributes attached to the element. If * there are no attributes, it shall be an empty * Attributes object. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see org.xml.sax.ContentHandler#startElement */
2.3 转为JavaBean
DOM和SAX两种解析XML的标准接口,使用起来都不直观。
幸运的,我们可以使用 Jackson
这个开源库把XML转化为JavaBean。
首选需要导入依赖包:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.10.3</version>
</dependency>
创建一个JavaBean Book.java
:
/*Book.java*/
public class Book {
private long id;
private String name;
private String category;
private String author;
private String isbn;
private List<String> tags;
private String pubDate;
//省略getter和setter
//省略toString
}
解析测试:
@Test
public void m2() throws IOException {
InputStream input = Main.class.getResourceAsStream("/book.xml");
JacksonXmlModule module = new JacksonXmlModule();
XmlMapper mapper = new XmlMapper(module);
Book book = mapper.readValue(input, Book.class);
System.out.println(book);
}
//输出结果
Book{id=1, name='Java核心技术', category='computer', author='Cay S. Horstmann', isbn='1234567', tags=[Java, Network], pubDate='2020-03-13'}