首页 > 代码库 > 第五章 以数据为中心—数据存取(2)

第五章 以数据为中心—数据存取(2)

5.1.2结构性的文件—读写XML文件

通过上面的介绍我们可以自由的操作Android中普通的文本文件了,下面我们来介绍下Android中比较常用的结构性的文件—XML文件。

XML,可扩展标记语言(Extensible Markup Language),用于标记电子文件,使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。

AndroidSDK提供了如下package来支持XML的读写:

l  javax.xml  根据XML规范定义核心XML常量和功能。

l  javax.xml.parsers  提供DOM和SAX方法解析XML文档。

l  org.w3c.dom  W3C提供的使用DOM方法读取XML。

l  org.xml.sax  提供核心SAX APIs。

l  org.xmlpull.v1  PULL解析器。

后面三个包中分别是android自带的三个XML解析器,有PULL、SAX(Simple API for XML)、DOM解析器。其中PULL跟SAX都是以事件作为驱动导向的解析器,优点是占用内存小,处理速度快。DOM是将整个XML放入内存中再解析,处理速度要稍差一些,但DOM也有自己的优点,可以在解析的时候适当增加节点。

在这里对这3种方法分别加以说明。

首先我们来看看我们需要解析的示例XML文档:

<?xml version="1.0" encoding="UTF-8"?>   

<persons>   

    <person id = "1">   

        <name>小王</name>   

        <age>20</age>     

    </person>           

    <person id = "2">   

        <name>小明</name>   

        <age>30</age>    

    </person>           

    <person id = "3">   

        <name>小丽</name>   

        <age>40</age>    

</person>  

</persons>

 

--------------------------------------------
程序员赚钱不易 一定要学会理财
平安陆金所 隶属于平安集团的p2p平台
年投资回报率7%-9% 是替代银行理财的首选
个人经验 推荐投资安鑫或者有担保的彩虹项目
不要投资安e 那个几乎无法转让 想提前提现非常困难
网站链接 http://affiliate.lufax.com/action/36XBU
首次投资1000元即可额外赚几百元 不赚白不赚
--------------------------------------------

然后我们在代码中创建一个与XML子节点对应的模型类。

public class Person {

 

    protected String id;

    protected String name;

    protected String age;

 

    public String getId() {

        return id;

    }

    public void setId(String id) {

        this.id = id;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public String getAge() {

        return age;

    }

    public void setAge(String age) {

        this.age = age;

    }

}

 

下面我们就将对这个XML用不同的方法来解析。

 

先来看如何采用DOM方式进行解析。

采用DOM的方法,读取XML文档的思路,这基本上与XML的结构是完全一样的。首先加载XML文档(Document),然后获取文档的根结点(Element),然后获取根结点中所有子节点的列表(NodeList),然后使用再获取子节点列表中的需要读取的结点。

根据以上思路,简要写个读取XML文件的方法如下:

1)实现DomHandler。

// import略

public class DomHandler { 

 

    private InputStream input; 

    private List<Person> persons; 

private Person person; 

 

    public DomHandler() { 

    } 

    public DomHandler(InputStream input) { 

        this.input = input; 

    } 

    public void setInput(InputStream input) { 

        this.input = input; 

    } 

    public List<Person> getPersons(){ 

        persons = new ArrayList<Person>(); 

        DocumentBuilder builder = null;

        Document document = null;

        try { 

            // 通过Dom工厂方法建立Dom解析器 

            builder =

            DocumentBuilderFactory.newInstance().newDocumentBuilder(); 

            document = builder.parse(input); 

            Element element = document.getDocumentElement(); 

            // 取得节点<person>的节点列表 

            NodeList personNodes =element.getElementsByTagName("person"); 

            // 节点长度 

            int length = personNodes.getLength(); 

            for(int i = 0; i < length; i++){ 

                // 取得<person>的节点元素 

                Element personElement = (Element)personNodes.item(i); 

                person = new Person(); 

                // 取得<person id="1">中的id属性值 

                person.setId(personElement.getAttribute("id")); 

                // 继续向下,取得子节点列表,如<name><age>等等 

                NodeList childnodes = personElement.getChildNodes(); 

                int len = childnodes.getLength(); 

                for(int j = 0 ; j < len ; j++){ 

                    // 如果子节点是一个元素节点 

                    if(childnodes.item(j).getNodeType() == Node.ELEMENT_NODE){  

                        // 取得节点名称 

                        String nodeName = childnodes.item(j).getNodeName(); 

                        // 取得节点值 

                        String nodeValue = http://www.mamicode.com/

                        childnodes.item(j).getFirstChild().getNodeValue(); 

                        if("name".equals(nodeName)){ 

                            person.setName(nodeValue); 

                        } 

                        if("age".equals(nodeName)){ 

                            person.setAge(nodeValue); 

                        }

                    } 

                } 

                persons.add(person); 

            }

            return persons; 

        } catch (Exception e) { 

            e.printStackTrace();

        } finally { 

            document = null; 

            builder = null; 

        }

        return null; 

    } 

}

 

2)使用DomHandler进行解析。

public static List<Person> readXMLByDOM(String filePath) { 

    try { 

        FileInputStream fis = new FileInputStream(new File(filePath)); 

        DomHandler domHandler = new DomHandler(fis); 

        return domHandler.getPersons();

    } catch (Exception e) { 

        e.printStackTrace(); 

    } 

    return null; 

 

以上就是使用DOM方式解析XML文件的方法。

 

下面我们介绍如何采用SAX的方法对XML文件进行读取。

SAX采用基于事件驱动的处理方式,它将XML文档转换成一系列的事件,由单独的事件处理器来决定如何处理。为了了解如何使用SAXAPI处理XML文档,这里介绍一下SAX所使用的基于事件驱动的处理方式。

基于事件的处理方式主要围绕着事件源以及事件处理器来工作的。一个可以产生事件的对象被称为事件源,而可以对事件产生响应的对象就被叫做事件处理器 。事件源与事件处理对象是通过在事件源中的事件注册方法连接的。当事件源产生事件后,调用事件处理器相应的方法,一个事件获得处理。当在事件源调用事件处理器中特定方法的时候,会传递这个事件标志以及其响应事件的状态信息,这样事件处理器才能够根据事件信息来决定自己的行为。

在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通过parser()方法来开始解析XML文档,并根据文档的内容产生事件。而事件处理器则是org.xml.sax包中的ContentHander、DTDHander、ErrorHandler,以及 EntityResolver这4个接口,它们分别处理事件源在解析XML文档过程中产生的不同种类的事件。而事件源XMLReader与这4个事件处理器的连接是通过在XMLReader中的相应事件处理器注册方法setXXXX()来完成的,详细介绍请见表5-1所示。

处理器名称

处理事件

XMLReader注册方法

ContentHandler

跟文档内容有关的事件

1)文档的开始与结束

2)xml元素的开始与结束

3)可忽略的实体

4)名称空间前缀映射的开始和结束

5)处理指令

6)字符数据和可忽略的空格

setContentHandler(ContentHandler h)

ErrorHandler

处理XML文档时产生的错误

setErrorHandler(ErrorHandler h)

DTDHandler

处理对文档的DTD进行解析时产生的事件

setDTDHandler(DTDHandler h)

EntityResolover

处理外部实体

setEntityResolover(EntityResolover h)

表5-1 事件处理器介绍

 

以上的4个事件源处理器接口,在开发中没有必要直接从这4个接口直接继承,因为org.xml.sax.helper包为我们提供了类 DefaultHandler,其继承了这4个接口,在实际开发中直接从DefaultHandler继承并实现相关方法就可以了。

在这4个接口中,最重要的是ContentHanlder接口,下面就其中的方法加以说明,如下:

1)startDocument()

当遇到文档开始的时候,调用这个方法,可以在其中做一些预处理的工作。

2)endDocument()

与遇到文档开始的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。

3) startElement(String namespaceURI, String localName, String qName,Attributes atts)

当读到一个开始标签的时候,会触发这个方法。namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。通过atts可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理,当遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中,所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,都需要我们的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。

4)endElement(String uri, String localName, String name)

同理,在遇到结束标签的时候,调用这个方法。

5)characters(char[] ch, int start, int length)

这个方法用来处理在XML文件中读到的内容,第一个参数为文件的字符串内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就可以获取内容。

只要为SAX提供实现ContentHandler接口的类,那么该类就可以得到通知事件(实际上就是SAX调用了该类中的回调方法)。因为ContentHandler是一个接口,在使用的时候可能会有些不方便,因此,SAX还为其制定了一个Helper类:DefaultHandler,它实现了ContentHandler接口,但是其所有的方法体都为空,在实现的时候,你只需要继承这个类,然后重载相应的方法即可。

使用SAX解析android.xml的代码如下:

1)实现DefaultHandler接口。

// import略

public class SaxHandler extends DefaultHandler { 

 

    private List<Person> persons; 

private Person person; 

// tagName的作用是记录解析时的上一个节点名称 

private String tagName;

 

    public List<Person> getPersons(){ 

        return persons; 

    } 

    /**

     * 节点处理

     */ 

    @Override 

    public void characters(char[] ch, int start, int length) throws SAXException 

    { 

        String data = http://www.mamicode.com/new String(ch, start, length);

        if("name".equals(tagName)){ 

            person.setName(data); 

        } 

        if("age".equals(tagName)){ 

            person.setAge(data); 

        } 

    } 

    /**

     * 元素结束

     */ 

    @Override 

    public void endElement(String uri, String localName, String qName) 

            throws SAXException {  

        if("person".equals(localName)){ 

            persons.add(person); 

            person = null; 

        } 

        tagName = null; 

    } 

    /**

     * 文档开始

     */ 

    @Override 

    public void startDocument() throws SAXException { 

        persons = new ArrayList<Person>(); 

    } 

    /**

     * 元素开始

     */ 

    @Override 

    public void startElement(String uri, String localName, String qName, 

            Attributes attributes) throws SAXException { 

        if("person".equals(localName)){ 

            person = new Person(); 

            person.setId(attributes.getValue("id")); 

        } 

        // 将正在解析的节点名称赋给tagName

        tagName = localName;  

}

 

2)使用SaxHandler进行解析。

public static List<Person> readXMLBySAX(String file_path) { 

    try { 

        FileInputStream fis = new FileInputStream(new File(file_path)); 

        SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); 

        SaxHandler saxHandler = new SaxHandler(); 

        parser.parse(fis, saxHandler); 

        return saxHandler.getPersons(); 

    } catch (Exception e) { 

        e.printStackTrace(); 

    } 

    return null; 

 

下面继续介绍如何使用Pull方式实现XML文件的解析。

Pull解析器是一个开源的Java项目,Android系统内部解析XML文件均为此种方式。Pull 解析器的运行方式与SAX解析器相似,它提供了类似的事件(开始元素和结束元素),但我们需要使用parser.next()提取它们。事件将作为数值代码被发送,因此可以使用一个简单case-switch语句来实现。

1)实现解析类。

// import略

public class PullHandler { 

 

    private InputStream input; 

    private List<Person> persons; 

private Person person; 

 

    public PullHandler() { 

    } 

    public void setInput(InputStream input) { 

        this.input = input; 

    } 

    public PullHandler(InputStream input) { 

        this.input = input; 

    } 

    public List<Person> getPersons() { 

        try { 

            XmlPullParser parser = Xml.newPullParser(); 

            parser.setInput(input, "UTF-8"); 

            int eventType = parser.getEventType(); 

            while (eventType != XmlPullParser.END_DOCUMENT) { 

                switch (eventType) { 

                case XmlPullParser.START_DOCUMENT:

                    // 表示开始文档事件

                    persons = new ArrayList<Person>(); 

                    break; 

                case XmlPullParser.START_TAG:

                    // 开始标签

                    // parser.getName()获取节点的名称

                    String tag = parser.getName();

                    if ("person".equals(tag)) { 

                        person = new Person(); 

                         // 取得第一个属性值

                        String id = parser.getAttributeValue(0);

                        person.setId(id); 

                    } 

                    if (null != person) { 

                        if ("name".equals(tag)) { 

                            // 获取下一个text类型的节点

                            person.setName(parser.nextText()); 

                        } 

                        if ("age".equals(tag)) { 

                            person.setAge(parser.nextText()); 

                        } 

                    } 

                    break; 

                case XmlPullParser.END_TAG: 

                    // XmlPullParser.END_TAG:结束标签 

                    if ("person".equals(parser.getName())) { 

                        persons.add(person); 

                        person = null; 

                    } 

                    break; 

                } 

                // 继续下一个元素

                eventType = parser.next();

            } 

            input.close(); 

            return persons; 

        } catch (Exception e) { 

            e.printStackTrace(); 

        } 

        return null; 

}

 }

 

2)使用PullHandler进行解析。

public static List<Person> readXMLByPULL(String file_path) { 

    try { 

        FileInputStream fis = new FileInputStream(new File(file_path)); 

        PullHandler pullHandler = new PullHandler(fis); 

        return pullHandler.getPersons();  

    } catch (Exception e) { 

        e.printStackTrace(); 

    } 

           return null; 

 

经验分享:

如何在实际项目中选用DOM、SAX还是PULL方式,要根据具体的项目情况来决定。以下介绍下三种方式各自的优缺点及使用场合,开发者可以根据项目的具体情况做判断。

DOM(文档对象模型),为XML文档的解析定义了一组接口,解析器读入整个文档,然后构造一个驻留内存的树结构,然后代码就可以使用DOM接口来操组整个树结构,具体分析如下:

优点:整个文档树都在内存当中,便于操作;支持删除、修改、重新排列等多功能。

缺点:将整个文档调入内存(经常包含大量无用的节点),浪费时间和空间。

使用场合:一旦解析了文档还需要多次访问这些数据,而且资源比较充足(如内存、CPU等)。

为了解决DOM解析XML引起的这些问题,出现了SAX。

SAX解析XML文档为事件驱动,详细说明请阅读Android读写XML(中)–SAX 。当解析器发现元素开始、元素结束,文本、文档的开始或者结束时,发送事件,在程序中编写响应这些事件的代码,其特点如下:

优点:不用事先调入整个文档,占用资源少。尤其在嵌入式环境中,极力推荐采用SAX进行解析XML文档。

缺点:不像DOM一样将文档长期驻留在内存,数据不是持久的,事件过后,如没有保存数据,那么数据就会丢失。

使用场合:机器性能有限,尤其是在嵌入式环境,如Android,极力推荐采用SAX进行解析XML文档。

大多数时间,使用SAX是比较安全的,并且Android提供了一种传统的SAX使用方法,以及一个便捷的SAX包装器。如果XML文档比较小,那么DOM可能是一种比较简单的方法。如果XML文档比较大,但只需要文档的一部分,则XML Pull解析器可能是更为有效的方法。最后对于编写XML,Pull解析器包也提供了一种便捷的方法。

 

第五章 以数据为中心—数据存取(2)