XML的解析方式常见的有两种,一种是以事件为驱动的XML解析,比如SAX。另外一种是类似于树形属性结构的数据,比如DOM类以及在此基础上延伸升级的JDOM以及DOM4J。这两种解析XML的方式完全不同,是两种不同的机制,其主要对比可以见下表:
XML解析方法 | 优点 | 缺点 |
SAX | 1. 效率和性能较好,不需要读入全部数据。 2. 可以根据需求适时停止,相对灵活。 | 1. 需要程序员自己编写与XML文件相关标签的处理逻辑, 2. 当XML文件较为复杂的时候,代码逻辑比较繁琐。 3. 无法修改XML文件,只能读取。 |
DOM类 | 1. 允许对XML数据结构进行更改。 2. 大量使用集合类,方便开发。 | 1.要把整个XML文件读入内存,导致资源消耗较大。 |
一.SAX解析器简介
所谓SAX是(Simple API for XML),是一种事件驱动型的XML解析器,是一种边遍历边解析,自上而下依次解析的方法,即先遍历父标签,接着递归遍历子标签,直到子标签不存在。
SAX的解析步骤可以分为如下四步:
1. 工厂方法初始化解析器Parser。
2. XML文件转换成流数据Stream。
3. 自定义Handler的初始化。
4. 解析器Parser利用自定义Handler对Stream进行处理。
SAX解析器中最重要的一点是自定义Handler的初始化,其内部逻辑是整个SAX解析器的核心所在。正如表格所说,我们需要根据XML文件的内容来编写相关标签的处理逻辑,而自定义Handler的代码逻辑编写由要XML来确定。举例说明:
1. 一个待解析的XML文件User.XML如下所示:
<?xmlversion="1.0"encoding="UTF-8"?>
<users>
<userid="1">
<name>小红</name>
<age>19</age>
</user>
<userid="2">
<name>小明</name>
<age>17</age>
</user>
<userid ="3">
<name>小黄</name>
<age>18</age>
</user>
</users>
2.相对应的根据XML文件的内容我们需要定义一个JavaBean,来和XML文件标签内容一一对应,如下所示:
public classUser {
private intid;
private Stringname;
private intage;
public intgetId() {
return id;
}
public voidsetId(intid) {
this.id = id;
}
public String getName() {
return name;
}
public voidsetName(String name) {
this.name = name;
}
public intgetAge() {
return age;
}
public voidsetAge(intage) {
this.age = age;
}
}
3.根据JavaBean和XML文件内逻辑,我们自定义一个继承自DefaultHandler类的Handler,如下所示:
packageutils;
importjava.util.ArrayList;
importjava.util.List;
importorg.xml.sax.Attributes;
importorg.xml.sax.SAXException;
importorg.xml.sax.helpers.DefaultHandler;
publicclass UserParserHandler extends DefaultHandler {
//用于标记标签名字
private String tagName;
private User user;
//定义一个User类的列表
private List<User> userList;
public String getTagName() {
return tagName;
}
public void setTagName(String tagName) {
this.tagName = tagName;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User>userList) {
this.userList = userList;
}
@Override
public void startDocument() throws SAXException{
//该方法只调用了一次
this.userList = new ArrayList<User>();
System.out.println("startDocument方法只调用了一次");
}
@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
// 该方法值只调用一次
System.out.println("endDocument方法只调用了一次");
}
/*@purpose 标签(元素)解析的时候调用
* @paramuri:XML文件的命名空间
* @paramlocalName:标签的名字
* @paramqName:带有命名空间的标签的名字
* @paramattributes:标签的属性集
* @logic:如果标签名和目标标签名字相同,则把属性集给加入到Id中。
* 然后把当前处理的标签给标记出来
* */
@Override
public void startElement(String uri, StringlocalName, String qName,
Attributes attributes) throws SAXException{
// TODO Auto-generated method stub
// super.startElement(uri, localName, qName,attributes);
if (qName.equals("user")) {
user = new User();
for (int i = 0; i <attributes.getLength(); i++) {
System.out.println(attributes.getLocalName(i)+":"
+ attributes.getValue(i));
if("id".equals(attributes.getLocalName(i))) {
user.setId(Integer.parseInt(attributes.getValue(i)));
}
}
}
this.tagName = qName; // 把当前标签记录下来,用来解析特征元素用的
}
@Override
public void endElement(String uri, StringlocalName, String qName)
throws SAXException {
// TODO Auto-generated method stub
super.endElement(uri, localName, qName);
if (qName.equals("user")) {
this.userList.add(this.user);
}
this.tagName = null;
}
/*@purpose:解析标签内容的时候调用
* @param ch:读取到的文本节点的字节数组
* @param start:字节开始的位置,为0则表示开始
* @param length:字节数组的长度
* @logic:验证标签是否为空,接着开始针对不同的标签设置Java Bean的属性值
*
* */
@Override
public void characters(char[] ch, int start,int length)
throws SAXException {
// TODO Auto-generated method stub
// super.characters(ch, start, length);
if (this.tagName != null) {
String value = new String(ch, start,length); // 将当前TextNode转换为String
if (this.tagName.equals("name"))
user.setName(value);
else if (this.tagName.equals("age"))
user.setAge(Integer.parseInt(value));
}
}
}
1.正如上述代码所示:代码继承了DefaultHandler,并重写了5个函数,分别为:
1) StartDocment,整个XML遍历解析过程中,该方法只调用一次,可以用来对User类的集合进行初始化。
2) EndDocument,该方法也仅仅调用了一次,并没有多少实际的意义。
3) StartElement,该方法是整个SAX解析核心的开始,其主要逻辑是对父标签进行解析,并对User类变量进行初始化。然后对该标签进行标注。
4) Characters,该方法是紧跟着StartElement方法的,其作用是对相应子标签进行解析,并把结果赋值到User类中。
5) EndElement,该方法是把解析赋值后的User类变量添加到User类列表List中,并对解析的标签进行还原标注。
上述五个方法的执行逻辑如下:
startDocument-startElement-characters-endElement-endDocument
具体可以描述为:
1. 当XML文件开始解析的时候,启动startDocument函数,该函数初始化User对象列表并在整个过程中只被调用了一次。
2.按照上述流程走到characters的时候,此时如果该节点无子标签节点,则一遍遍历通过到endDocument,该节点标签的解析完成。如果该标签节点存在子节点标签,那么需要返回到startElement,即回调该方法。然后走完余下的流程。
3.整个XML的解析过程是一个回调递归的过程。
4.代码测试,完全按照上述四个解析步骤进行测试:
总结:SAX解析器是一种事件驱动的回调递归式的解析,当XML文件的逻辑相对简单,或者保存的为数据需要读取出来的时候,建议选择SAX解析器。