解析 XML 的方式
# 01.解析 XML 的方式
XML 是一种树形结构的文档,它有两种标准的解析 API:
- DOM:一次性读取 XML,并在内存中表示为树形结构;
- SAX:以流的形式读取 XML,使用事件回调。
# 使用 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>
2
3
4
5
6
7
8
9
10
11
如果解析为 DOM 结构,它大概长这样:
┌─────────┐
│document │
└─────────┘
│
▼
┌─────────┐
│ book │
└─────────┘
│
┌──────────┬──────────┼──────────┬──────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐┌─────────┐┌─────────┐┌─────────┐┌─────────┐
│ name ││ author ││ isbn ││ tags ││ pubDate │
└─────────┘└─────────┘└─────────┘└─────────┘└─────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ tag │ │ tag │
└───────┘ └───────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意到最顶层的 document 代表 XML 文档,它是真正的“根”,而 <book>
虽然是根元素,但它是 document
的一个子节点。
Java 提供了 DOM API 来解析 XML,它使用下面的对象来表示 XML 的内容:
- Document:代表整个 XML 文档;
- Element:代表一个 XML 元素;
- Attribute:代表一个元素的某个属性。
使用 DOM API 解析一个 XML 文档的代码如下:
InputStream input = Main.class.getResourceAsStream("/book.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);
2
3
4
DocumentBuilder.parse()
用于解析一个 XML,它可以接收 InputStream,File 或者 URL,如果解析无误,我们将获得一个 Document 对象,这个对象代表了整个 XML 文档的树形结构,需要遍历以便读取指定元素的值:
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
解析结构如下:
Document: #document
Element: book
Text: #text =
Element: name
Text: #text = Java核心技术
Text: #text =
Element: author
Text: #text = Cay S. Horstmann
Text: #text =
...
2
3
4
5
6
7
8
9
10
11
12
对于 DOM API 解析出来的结构,我们从根节点 Document 出发,可以遍历所有子节点,获取所有元素、属性、文本数据,还可以包括注释,这些节点被统称为 Node,每个 Node 都有自己的 Type,根据 Type 来区分一个 Node 到底是元素,还是属性,还是文本,等等。
使用 DOM API 时,如果要读取某个元素的文本,需要访问它的 Text 类型的子节点,所以使用起来还是比较繁琐的。
# 使用 SAX
使用 DOM 解析 XML 的优点是用起来省事,但它的主要缺点是内存占用太大。
另一种解析 XML 的方式是 SAX。SAX 是 Simple API for XML 的缩写,它是一种基于流的解析方式,边读取 XML 边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论 XML 有多大,占用的内存都很小。
SAX 解析会触发一系列事件:
- startDocument:开始读取 XML 文档;
- startElement:读取到了一个元素,例如
<book>
; - characters:读取到了字符;
- endElement:读取到了一个结束的元素,例如
</book>
; - endDocument:读取 XML 文档结束。
如果我们用 SAX API 解析 XML,Java 代码如下:
InputStream input = Main.class.getResourceAsStream("/book.xml");
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(input, new MyHandler());
2
3
4
关键代码 SAXParser.parse()
除了需要传入一个 InputStream
外,还需要传入一个回调对象,这个对象要继承自 DefaultHandler
:
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
运行 SAX 解析代码,可以打印出下面的结果:
start document
start element: book
characters:
start element: name
characters: Java核心技术
end element: name
characters:
start element: author
...
2
3
4
5
6
7
8
9
10
11
如果要读取 <name>
节点的文本,我们就必须在解析过程中根据 startElement()
和 endElement()
定位当前正在读取的节点,可以使用栈结构保存,每遇到一个 startElement()
入栈,每遇到一个 endElement()
出栈,这样,读到 characters()
时我们才知道当前读取的文本是哪个节点的。可见,使用 SAX API 仍然比较麻烦。
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02