1 概念
Java的xml解析器库有很多,万变不离其宗的就是SAX和DOM解析器。
1.1 DOM解析
DOM的全称是Document Object Model,也即文档对象模型。在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作。通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制。
DOM接口提供了一种通过分层对象模型来访问XML文档信息的方式,这些分层对象模型依据XML的文档结构形成了一棵节点树。无论XML文档中所描述的是什么类型的信息,即便是制表数据、项目列表或一个文档,利用DOM所生成的模型都是节点树的形式。也就是说,DOM强制使用树模型来访问XML文档中的信息。由于XML本质上就是一种分层结构,所以这种描述方法是相当有效的。
DOM树所提供的随机访问方式给应用程序的开发带来了很大的灵活性,它可以任意地控制整个XML文档中的内容。然而,由于DOM分析器把整个XML文档转化成DOM树放在了内存中,因此,当文档比较大或者结构比较复杂时,对内存的需求就比较高。而且,对于结构复杂的树的遍历也是一项耗时的操作。所以,DOM分析器对机器性能的要求比较高,实现效率不十分理想。不过,由于DOM分析器所采用的树结构的思想与XML文档的结构相吻合,同时鉴于随机访问所带来的方便,因此,DOM分析器还是有很广泛的使用价值的。
1.2 SAX
SAX采用的事件模型,SAX是Simple API for XML的缩写,这种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。
1.3 Pull解析
Pull解析和Sax解析很相似,都是轻量级的解析,在Android的内核中已经嵌入了Pull,所以我们不需要再添加第三方jar包来支持Pull。
2 区别
2.1 DOM解析
解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM接口来操作这个树结构。
(1)优点:形成了树结构,有助于更好的理解、掌握,且代码容易编写;整个文档树在内存中,便于修改。
(2)缺点:由于文件是一次性读取,所以对内存的耗费比较大;如果XML文件比较大,容易影响解析性能且可能会造成内存溢出。
(3)使用场合:一旦解析了文档还需多次访问这些数据;硬件资源充足(内存、CPU)
2.2 SAX
事件驱动。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件;编写响应这些事件的代码,保存数据。注意:SAX 解析器不创建任何对象。
(1)优点:不用事先调入整个文档,对内存耗费比较小。;
(2)缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素;
(3)使用场合:只需XML文档的少量内容,很少回头访问;一次性读取;机器内存少。
3 使用例子
3.1 准备
(1)Xml文件内容
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="12">
<name>thinking in java</name>
<price>85.5</price>
</book>
<book id="15">
<name>Spring in Action</name>
<price>39.0</price>
</book>
</books>
(2)Book.java如下:主要是用来组装数据
public class Book {
private String id;
private String num;
private String name;
private String price;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"id='" + id + '\'' +
", num='" + num + '\'' +
", name='" + name + '\'' +
", price='" + price + '\'' +
'}';
}
}
3.2 Dom解析
当我们得到节点book时,也就是图中1所画的地方,如果我们调用它的getChildNodes()方法,大家猜猜它的子节点有几个?不包括它的孙子节点,thinking in java这种的除外,因为它是孙子节点。它总共有5个子节点,分别是图中2、3、4、5、6所示的那样。所以在解析时,一定要小心,不要忽略空白的地方。
// 解析book.xml文件DomParseService.java
public class DomParseService {
public List<Book> getBooks(InputStream inputStream) throws Exception{
List<Book> list = new ArrayList<Book>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream);
Element element = document.getDocumentElement();
NodeList bookNodes = element.getElementsByTagName("book");
for(int i=0;i<bookNodes.getLength();i++){
Element bookElement = (Element) bookNodes.item(i);
Book book = new Book();
book.setId(Integer.parseInt(bookElement.getAttribute("id")));
NodeList childNodes = bookElement.getChildNodes();
// System.out.println("*****"+childNodes.getLength());
for(int j=0;j<childNodes.getLength();j++){
if(childNodes.item(j).getNodeType()==Node.ELEMENT_NODE){
if("name".equals(childNodes.item(j).getNodeName())){
book.setName(childNodes.item(j).getFirstChild().getNodeValue());
}else if("price".equals(childNodes.item(j).getNodeName())){
book.setPrice(Float.parseFloat(childNodes.item(j).getFirstChild().getNodeValue()));
}
}
} //end for j
list.add(book);
} //end for i
return list;
}
}
// 测试使用单元测试如下ParseTest.java
public class ParseTest extends TestCase{
public void testDom() throws Exception{
InputStream input = this.getClass().getClassLoader().getResourceAsStream("book.xml");
DomParseService dom = new DomParseService();
List<Book> books = dom.getBooks(input);
for(Book book : books){
System.out.println(book.toString());
}
}
}
3.3 Sax解析
按照xml文件的顺序一步一步的来解析,在解析xml文件之前,我们要先了解xml文件的节点的种类,一种是ElementNode,一种是TextNode。其中,像books、book这种节点就属于ElementNode,而thinking in java、85.5这种就属于TextNode。注意:在用Sax解析的时候最需要重视的一点就是不要把那些<节点>之间的空白忽略就好!
public class SaxParseService extends DefaultHandler{
private List<Book> books = null;
private Book book = null;
private String preTag = null;//作用是记录解析时的上一个节点名称
public List<Book> getBooks(InputStream xmlStream) throws Exception{
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
SaxParseService handler = new SaxParseService();
parser.parse(xmlStream, handler);
return handler.getBooks();
}
public List<Book> getBooks(){
return books;
}
@Override
public void startDocument() throws SAXException {
books = new ArrayList<Book>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if("book".equals(qName)){
book = new Book();
book.setId(Integer.parseInt(attributes.getValue(0)));
}
preTag = qName;//将正在解析的节点名称赋给preTag
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if("book".equals(qName)){
books.add(book);
book = null;
}
preTag = null;
/**当解析结束时置为空。这里很重要,例如,当图中画3的位置结束后,会调用这个方法
,如果这里不把preTag置为null,根据startElement(....)方法,preTag的值还是book,当文档顺序读到图
中标记</book>的位置时,会执行characters(char[] ch, int start, int length)这个方法,而characters(....)方
法判断preTag!=null,会执行if判断的代码,这样就会把空值赋值给book,这不是我们想要的。*/
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if(preTag!=null){
String content = new String(ch,start,length);
if("name".equals(preTag)){
book.setName(content);
}else if("price".equals(preTag)){
book.setPrice(Float.parseFloat(content));
}
}
}
}
// 测试代码如下:ParseTest
public class ParseTest extends TestCase{
public void testSAX() throws Throwable{
SaxParseService sax = new SaxParseService();
InputStream input = this.getClass().getClassLoader().getResourceAsStream("book.xml");
List<Book> books = sax.getBooks(input);
for(Book book : books){
System.out.println(book.toString());
}
}
}
3.4 Pull解析
(1)book.xml,保存在asserts下
<?xml version="1.0" encoding="UTF-8"?> <!-- START_DOCUMENT,文档开始标签 -->
<books> <!-- START_TAG,开始标签,标签名通过getName()读取 -->
<book id="book1" num="1"> <!-- 属性名-id,通过getAttributeName(int index)读取 -->
<name>thinking in java</name>
<price>85.5</price>
</book>
<book id="book2" num="2"> <!-- 属性值-“15”,通过getAttributeValue(int index)或getAttributeValue(String namespace,String name)读取 -->
<name>Spring in Action</name>
<price>39.0</price>
</book>
<node>Text字段</node> <!-- TEXT,文本标签,通过getText()读取 -->
</books> <!-- END_TAG,结束标签 -->
(2)MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Book> books = praseBook();
for(Book book : books){
Log.e("TAG", book.toString());
}
}
private List<Book> praseBook() {
List<Book> books = null;
Book book = null;
XmlPullParser parser = Xml.newPullParser();
InputStream input = null;
try {
input = getAssets().open("book.xml");
parser.setInput(input, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
try {
//产生第一个事件
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
switch (event) {
//判断当前事件是否是文档开始事件
case XmlPullParser.START_DOCUMENT:
books = new ArrayList<>();
break;
//判断当前事件是否是标签元素开始事件
case XmlPullParser.START_TAG:
//判断开始标签元素是否是book元素
if ("book".equals(parser.getName())) {
book = new Book();
//得到book标签的属性值
book.setId(parser.getAttributeValue(0));
book.setNum(parser.getAttributeValue(null,"num"));
// book.setNum(parser.getAttributeValue(1));
}
if (book != null) {
//判断开始标签元素是否是name元素
if ("name".equals(parser.getName())) {
book.setName(parser.nextText());
//判断开始标签元素是否是price元素
} else if ("price".equals(parser.getName())) {
book.setPrice(parser.nextText());
}
}
break;
case XmlPullParser.TEXT:
// if (!TextUtils.isEmpty(parser.getText())) {
// Log.e("TAG", "parser.getText():" + parser.getText());
// }
break;
//判断当前事件是否是标签元素结束事件
case XmlPullParser.END_TAG:
//判断结束标签元素是否是book元素
if ("book".equals(parser.getName()) && books != null) {
books.add(book);
book = null;
}
break;
default:
break;
}
//进入下一个元素并触发相应事件
event = parser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return books;
}
}
4 参考文档
Java解析xml的主要解析器: SAX和DOM的选择(附上新方法–Pull解析)