Java进阶之Sax、Dom和Pull解析xml

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下

扫描二维码关注公众号,回复: 1979285 查看本文章
<?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解析)

XML解析——Java中XML的四种解析方式

【Android学习笔记】XmlResourceParser解析xml文件

android之Xml的高效解析方式——pull

猜你喜欢

转载自blog.csdn.net/chenliguan/article/details/79293640