首页 > 代码库 > 第五章 以数据为中心—数据存取(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> |
然后我们在代码中创建一个与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)