我的Android进阶之旅------>Android中解析XML 技术详解---->SAX解析、DOM解析、PULL解析

来源:互联网 发布:宁波网络机柜回收 编辑:程序博客网 时间:2024/04/30 10:53

XML在各种开发中都广泛应用,Android也不例外。作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能。今天就由我向大家介绍一下在Android平台下几种常见的XML解析和创建的方法。

在Android中,常见的XML解析器分别为SAX解析器、DOM解析器和PULL解析器,下面,我将一一向大家详细介绍。

SAX解析器:

SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。

SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。

DOM解析器:

DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。

由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。

PULL解析器:

PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。

以上三种解析器,都是非常实用的解析器,我将会一一介绍。我们将会使用这三种解析技术完成一项共同的任务。

我们新建一个项目,项目结构如下:

我在项目的src目录中放置一个XML文档persons.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?><persons><person id="1"><name>sax</name><age>30</age></person><person id="2"><name>dom</name><age>40</age></person><person id="3"><name>pull</name><age>50</age></person><person id="4"><name>ouyangpeng</name><age>60</age></person><person id="5"><name>chengming</name><age>70</age></person><person id="6"><name>just for test</name><age>80</age></person></persons>

然后我们分别使用以上三种解析技术解析文档,得到一个List<Person>的对象,先来看一下Person.java的代码:

package cn.roco.xml.domain;public class Person {private Integer id;private String name;private Integer age;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Override     public String toString() {         return "id:" + id + ", name:" + name + ", age:" + age;     }  }


 

接下来,就该介绍操作过程了,我们先为解析器定义一个IPersonService接口,每种类型的解析器需要实现此接口。IPersonService.java代码如下:
package cn.roco.xml.service;import java.io.InputStream;import java.io.OutputStream;import java.util.List;import cn.roco.xml.domain.Person;public interface IPersonService {/** * 获取数据 * @param is  输入方向  * @return   数据 * @throws Exception */public List<Person> parse(InputStream is) throws Exception;/** * 保存数据 * @param persons  数据 * @param out  输出方向 * @throws Exception */public void serialize(List<Person> persons, OutputStream out)throws Exception;}

好了,我们就该一个一个的实现该接口,完成我们的解析过程。

使用SAX解析器:

PersonServiceImpBySax.java代码如下:

package cn.roco.xml.service.imp;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import javax.xml.transform.OutputKeys;import javax.xml.transform.Result;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerFactory;import javax.xml.transform.sax.SAXTransformerFactory;import javax.xml.transform.sax.TransformerHandler;import javax.xml.transform.stream.StreamResult;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.AttributesImpl;import org.xml.sax.helpers.DefaultHandler;import cn.roco.xml.domain.Person;import cn.roco.xml.service.IPersonService;public class PersonServiceImpBySax implements IPersonService {@Overridepublic void serialize(List<Person> persons, OutputStream out)throws Exception {SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();// 取得SAXTransformerFactory实例TransformerHandler handler = factory.newTransformerHandler(); // 从factory获取TransformerHandler实例Transformer transformer = handler.getTransformer(); // 从handler获取Transformer实例transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // 设置输出采用的编码方式transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 是否自动添加额外的空白transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 是否忽略XML声明Result result=new StreamResult(out);handler.setResult(result);String uri = ""; // 代表命名空间的URI 当URI无值时 须置为空字符串String localName = ""; // 命名空间的本地名称(不包含前缀) 当没有进行命名空间处理时 须置为空字符串handler.startDocument();handler.startElement(uri, localName, "persons", null);AttributesImpl attrs = new AttributesImpl(); // 负责存放元素的属性信息char[] ch = null;for (Person person : persons) {attrs.clear();// 清空属性列表attrs.addAttribute(uri, localName, "id", "string",String.valueOf(person.getId()));// 添加一个名为id的属性(type影响不大,这里设为string)handler.startElement(uri, localName, "person", attrs); // 开始一个person元素// 关联上面设定的id属性handler.startElement(uri, localName, "name", null); // 开始一个name元素   没有属性ch = String.valueOf(person.getName()).toCharArray();handler.characters(ch, 0, ch.length); // 设置name元素的文本节点handler.endElement(uri, localName, "name");handler.startElement(uri, localName, "age", null); // 开始一个name元素   没有属性ch = String.valueOf(person.getAge()).toCharArray();handler.characters(ch, 0, ch.length); // 设置name元素的文本节点handler.endElement(uri, localName, "age");handler.endElement(uri, localName, "person");}handler.endElement(uri, localName, "persons");handler.endDocument();}@Overridepublic List<Person> parse(InputStream is) throws Exception {SAXParserFactory factory = SAXParserFactory.newInstance();// 取得SAXParserFactory实例SAXParser saxParser = factory.newSAXParser(); // 从factory获取SAXParser实例MyHandler handler = new MyHandler();// 实例化自定义HandlersaxParser.parse(is, handler); // 根据自定义Handler规则解析输入流return handler.getPersons();}private final class MyHandler extends DefaultHandler {private List<Person> persons;private Person person;private StringBuilder builder;public List<Person> getPersons() {return persons;}@Overridepublic void startDocument() throws SAXException {super.startDocument();persons = new ArrayList<Person>();builder = new StringBuilder();}@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {super.startElement(uri, localName, qName, attributes);if ("person".equals(localName)) {person = new Person();Integer id = Integer.parseInt(attributes.getValue(0));person.setId(id);}builder.setLength(0);// 将字符长度设置为0 以便重新开始读取元素内的字符节点}@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {super.characters(ch, start, length);builder.append(ch, start, length); // 将读取的字符数组追加到builder中}@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {super.endElement(uri, localName, qName);if ("name".equals(localName)) {person.setName(builder.toString());} else if ("age".equals(localName)) {person.setAge(Integer.parseInt(builder.toString()));} else if ("person".equals(localName)) {persons.add(person);}}}}


 

代码中,我们定义了自己的事件处理逻辑,重写了DefaultHandler的几个重要的事件方法。下面我为大家着重介绍一下DefaultHandler的相关知识。DefaultHandler是一个事件处理器,可以接收解析器报告的所有事件,处理所发现的数据。它实现了EntityResolver接口、DTDHandler接口、ErrorHandler接口和ContentHandler接口。这几个接口代表不同类型的事件处理器。我们着重介绍一下ContentHandler接口。结构如图:

这几个比较重要的方法已被我用红线标注,DefaultHandler实现了这些方法,但在方法体内没有做任何事情,因此我们在使用时必须覆写相关的方法。最重要的是startElement方法、characters方法和endElement方法。当执行文档时遇到起始节点,startElement方法将会被调用,我们可以获取起始节点相关信息;然后characters方法被调用,我们可以获取节点内的文本信息;最后endElement方法被调用,我们可以做收尾的相关操作。

 

最后,我们需要调用SAX解析程序,这个步骤在MainActivity中完成:

package cn.roco.xml;import java.io.FileOutputStream;import java.io.InputStream;import java.util.List;import cn.roco.xml.domain.Person;import cn.roco.xml.service.IPersonService;import cn.roco.xml.service.imp.PersonServiceImpByDom;import cn.roco.xml.service.imp.PersonServiceImpByPull;import cn.roco.xml.service.imp.PersonServiceImpBySax;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {TextView display;List<Person> persons = null;IPersonService personService = null;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);// 指定IPersonService由PersonServiceImpByPull实现// personService = new PersonServiceImpByPull();// 指定IPersonService由PersonServiceImpBySax实现personService = new PersonServiceImpBySax();// 指定IPersonService由PersonServiceImpByDom实现//personService = new PersonServiceImpByDom();Button readButton = (Button) findViewById(R.id.readButton);Button saveButton = (Button) findViewById(R.id.saveButton);display = (TextView) findViewById(R.id.display);readButton.setOnClickListener(new ReadButtonOnClickListener());saveButton.setOnClickListener(new SaveButtonOnClickListener());}private final class SaveButtonOnClickListener implementsView.OnClickListener {@Overridepublic void onClick(View view) {try {FileOutputStream out = openFileOutput("persons_backup.xml",Context.MODE_PRIVATE);personService.serialize(persons, out);Toast.makeText(getApplicationContext(), R.string.save_succ, 1).show();} catch (Exception e) {Toast.makeText(getApplicationContext(), R.string.save_fail, 1).show();e.printStackTrace();}}}private final class ReadButtonOnClickListener implementsView.OnClickListener {@Overridepublic void onClick(View v) {InputStream xml = this.getClass().getClassLoader().getResourceAsStream("person.xml");try {persons = personService.parse(xml);} catch (Exception e) {Toast.makeText(getApplicationContext(), R.string.read_fail, 1).show();e.printStackTrace();}display.setText("");for (Person person : persons) {display.append(person.toString() + "\n");}}}}


 

界面就两个按钮,顺便给大家贴上:

点击“readXML”按钮,将会调用SAX解析器解析文档,并在TextView中显示相关信息,如下图:

然后再点击“保存数据”按钮,将会在该应用包下的files目录生成一个person_backup.xml文件:

将persons_backup.xml文件导出到桌面上,格式化后如图所示:

 

使用DOM解析器:

PersonServiceImpByDom.java代码如下:

 

package cn.roco.xml.service.imp;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import javax.xml.transform.OutputKeys;import javax.xml.transform.Result;import javax.xml.transform.Source;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerFactory;import javax.xml.transform.dom.DOMSource;import javax.xml.transform.sax.SAXTransformerFactory;import javax.xml.transform.sax.TransformerHandler;import javax.xml.transform.stream.StreamResult;import org.w3c.dom.Attr;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.AttributesImpl;import org.xml.sax.helpers.DefaultHandler;import android.R.bool;import cn.roco.xml.domain.Person;import cn.roco.xml.service.IPersonService;public class PersonServiceImpByDom implements IPersonService {@Overridepublic void serialize(List<Person> persons, OutputStream out)throws Exception {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();Document doc = builder.newDocument(); // 由builder创建新文档Element rootElement = doc.createElement("persons");for (Person person : persons) {Element personElement = doc.createElement("person");Attr id=doc.createAttribute("id"); //创建Id属性节点id.setValue( person.getId().toString()); //给属性赋值personElement.setAttributeNode(id);//把id属性节点追加到personElement nameElement = doc.createElement("name");nameElement.setTextContent(person.getName());personElement.appendChild(nameElement);Element ageElement = doc.createElement("age");ageElement.setTextContent(person.getAge().toString());personElement.appendChild(ageElement);rootElement.appendChild(personElement);}doc.appendChild(rootElement);TransformerFactory transFactory = TransformerFactory.newInstance();// 取得TransformerFactory实例Transformer transformer = transFactory.newTransformer(); // 从transFactory获取Transformer实例transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // 设置输出采用的编码方式transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 是否自动添加额外的空白transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 是否忽略XML声明Source source=new DOMSource(doc);Result result=new StreamResult(out);transformer.transform(source, result);}@Overridepublic List<Person> parse(InputStream is) throws Exception {List<Person> persons = new ArrayList<Person>();DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 取得DocumentBuilderFactory实例DocumentBuilder builder = factory.newDocumentBuilder(); // 从factory获取DocumentBuilder实例Document doc = builder.parse(is); // 解析输入流 得到Document实例Element rootElement = doc.getDocumentElement();NodeList items = rootElement.getElementsByTagName("person");for (int i = 0; i < items.getLength(); i++) {Element item = (Element) items.item(i);Person person = new Person();person.setId(Integer.parseInt(item.getAttribute("id")));NodeList properties = item.getChildNodes();for (int j = 0; j < properties.getLength(); j++) {if (properties.item(j).getNodeType() == Node.ELEMENT_NODE) {Node property = properties.item(j);String nodeName = property.getNodeName();if ("name".equals(nodeName)) {person.setName(property.getFirstChild().getNodeValue());} else if ("age".equals(nodeName)) {person.setAge(Integer.parseInt(property.getFirstChild().getNodeValue()));}}}persons.add(person);}return persons;}}


 

然后再MainActivity中只需改一个地方:

// 指定IPersonService由PersonServiceImpByPull实现// //personService = new PersonServiceImpByPull();// 指定IPersonService由PersonServiceImpBySax实现////personService = new PersonServiceImpBySax();// 指定IPersonService由PersonServiceImpByDom实现personService = new PersonServiceImpByDom();

执行结果是一样的。

 

 

 

使用PULL解析器:

PersonServiceImpByPull.java代码如下:

package cn.roco.xml.service.imp;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;import org.xmlpull.v1.XmlPullParser;import org.xmlpull.v1.XmlSerializer;import android.util.Xml;import cn.roco.xml.domain.Person;import cn.roco.xml.service.IPersonService;public class PersonServiceImpByPull implements IPersonService {/** * 保存数据 序列号 *  * @param persons *            数据 * @param out *            输出方向 * @throws Exception */public void serialize(List<Person> persons, OutputStream out)throws Exception {XmlSerializer serializer = Xml.newSerializer(); // 由android.util.Xml创建一个XmlSerializer实例serializer.setOutput(out, "UTF-8");serializer.startDocument("UTF-8", true);serializer.startTag(null, "persons");for (Person person : persons) {serializer.startTag(null, "person");serializer.attribute(null, "id", person.getId().toString());serializer.startTag(null, "name");serializer.text(person.getName());serializer.endTag(null, "name");serializer.startTag(null, "age");serializer.text(person.getAge().toString());serializer.endTag(null, "age");serializer.endTag(null, "person");}serializer.endTag(null, "persons");serializer.endDocument();out.flush();out.close();}/** * 获取数据 解析XML *  * @param is *            输入方向 * @return 数据 * @throws Exception */public List<Person> parse(InputStream is) throws Exception {List<Person> persons = null;Person person = null;// XmlPullParserFactory factory = XmlPullParserFactory.newInstance();// XmlPullParser pullParser = factory.newPullParser();XmlPullParser pullParser = Xml.newPullParser();// 由android.util.Xml创建一个XmlPullParser实例pullParser.setInput(is, "UTF-8");// 设置输入流 并指明编码方式int eventType = pullParser.getEventType();while (eventType != XmlPullParser.END_DOCUMENT) {switch (eventType) {case XmlPullParser.START_DOCUMENT:persons = new ArrayList<Person>();break;case XmlPullParser.START_TAG:if ("person".equals(pullParser.getName())) {person = new Person();person.setId(Integer.parseInt(pullParser.getAttributeValue(0)));}if ("name".equals(pullParser.getName())) {person.setName(pullParser.nextText());}if ("age".equals(pullParser.getName())) {person.setAge(Integer.parseInt(pullParser.nextText()));}break;case XmlPullParser.END_TAG:if ("person".equals(pullParser.getName())) {persons.add(person);person = null;}break;}eventType = pullParser.next();}return persons;}}


 

然后再对MainActivity做以下更改:

       // 指定IPersonService由PersonServiceImpByPull实现personService = new PersonServiceImpByPull();// 指定IPersonService由PersonServiceImpBySax实现// personService = new PersonServiceImpBySax();// 指定IPersonService由PersonServiceImpByDom实现// personService = new PersonServiceImpByDom();


 

和其他两个执行结果都一样。

对于这三种解析器各有优点,我个人比较倾向于PULL解析器,因为SAX解析器操作起来太笨重,DOM不适合文档较大,内存较小的场景,唯有PULL轻巧灵活,速度快,占用内存小,使用非常顺手。读者也可以根据自己的喜好选择相应的解析技术。

 


==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================