1.xml 是什么
全名叫 可扩展标记语言,一般用来存储数据,可以看作一个微型的数据库,比如 SharePreference 就是适用 xml 文件保存配置信息,SQLite 的底层也是一个 xml 文件。把数据包装成一个 xml 文件进行传递也是常用的做法。
2.xml 文件的内容结构
<?xml version="1.0" encoding="UTF-8"?>(文档声明,声明当前文档为 xml 格式,采用 UTF-8 编码)
<persons>(开始元素(persons))
<person id="1">(文本节点(空白文本) 开始元素(person) 属性)
<name>nameA</name>(文本节点(空白文本) 开始元素(name) 文本节点 结束元素(name))
<age>ageOne</age>(文本节点(空白文本) 开始元素(age) 文本节点 结束元素(age))
</person>(文本节点(空白文本) 结束元素(person))
<person id="2">
<name>nameB</name>
<age>ageTwo</age>
</person>
</persons>(结束元素(persons))
!缩进位置的空白处也是文本节点,为空白文本。
3.解析 xml 文件的三种方法
1.SAX:
- 处理形式:对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束等地方时通知事件处理函数,由事件处理函数执行相应动作,然后继续同样的扫描,直到文档末尾。采用流式解析,解析是同步的,读到哪里就处理到哪里。
- 优点:解析速度快,占用内存少。
- 缺点:每需要解析一类xml,就需要编写新的适合该类xml的处理类,用起来比较麻烦。
- 用法:继承 Android 提供的帮助类 DefaultHandler ,重写对应 的方法:
startDocument(); //当读取到文档开始标志时触发,通常在这里完成一些初始化操作
endDocument(); //文档结束部分,在这里完成一些善后工作
startElement(String uri, String localName, String qName, Attributes attributes); //参数依次为(命名空间,不带命名空间的前缀标签名,带命名空间前缀的标签名,通过atts可以得到所有的属性名和相应的值), SAX 中一个重要的特点就是它的流式处理,当遇到一个标签的时候,它并不会记录下以前所碰到的标签,也就是说,在 startElement() 方法中,所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子属性等等其它与结构相关的信息,都是不得而知的,都需要你的程序来完成,这使得 SAX 在编程处理上没有 DOM 来得方便。
endElement(String uri, String localName, String qName); //在遇到结束标签时,调用这个方法
charracters(char[] ch, int start, int length) //这个方法用来处理在 xml 文件中读到的内容,第一个参数用于存放文件的内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用 new String(char[] ch, int start, int length) 就可以获取内容
代码栗子:
创建一个 xml 文件: person.xml ,放到 assets 目录下:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person id = "11">
<name>SAX解析</name>
<age>18</age>
</person>
<person if = "13">
<name>XML1</name>
<age>43</age>
</person>
</persons>
SAX 解析类: SAXHelper.java:
public class SAXHelper extends DefaultHandler {
private Person person;
private ArrayList<Person> persons;
//当前解析的元素标签
private String tagName = null;
//当读取到文档开始标志时触发,通常在这里完成一些初始化操作
@Override
public void startDocument() throws SAXException {
this.persons = new ArrayList<Person>();
Log.d("SAX", "读取到文档头,开始解析 xml");
}
//读到一个开始标签时触发,第二个参数为标签名,最后一个参数为属性数组
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (localName.equals("person")){
person = new Person();
person.setId(Integer.parseInt(attributes.getValue("id")));
Log.d("SAX", "开始处理person元素");
}
this.tagName = localName;
}
//读取内容,第一个参数为字符串内容,后面依次为起始位置与长度
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//判断当前标签是否有效
if (this.tagName != null) {
String data = new String(ch, start, length);
//读取标签中的内容
if (this.tagName.equals("name")) {
this.person.setName(data);
Log.d("SAX", "处理 name 元素内容");
}else if (this.tagNmae.equals("age")) {
this.person.setAge(Integer.parseInt(data));
Log.d("SAX", "处理 age 元素内容");
}
}
}
//处理元素结束时触发,这里讲对象添加到集合中
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (localName.equals("person")) {
this.persons.add(person);
person = null;
Log.d("SAX", "处理 person 元素结束");
}
this.tagName = null;
}
//读取到文档结尾时触发
@Override
public void endDocument() throws SAXException {
super.endDocument();
Log.i("SAX", "读取到文档尾, xml 解析结束");
}
//获取 person 集合
public ArrayList<Person> getPersons() {
return persons;
}
}
在 MainActivity.java 中写上这样一个方法,要解析 xml 时就调用
private ArrayList<Person> readxmlForSAX() throws Exception {
//获取文件资源建立输入流对象
InputStraem is = getAssets().open("person.xml");
//创建 SAX 解析类对象
SAXHelper ss = new SAXHelper();
//得到 SAX 解析工厂
SAXParserFactory factory = SAXParseFactory.newInstance();
//创建 SAX 解析器
SAXParser parser = factory.newSAXParser();
//将 xml 解析处理器分配给解析器,对文档进行解析,将事件发送给处理器
parser.parse(is, ss);
is.close();
return ss.getPersons();
}
2.DOM:
- 处理形式:先把 xml 文档都读取到内存中,然后再用 DOM API 来访问树形结构,并获取数据。
- 优点:写起来简单
- 缺点:很消耗内存,假如读取的数据量较大,手机内存不够的话,可能导致手机死机,建议只用于解析内容少的 xml 文件
- 用法:
API:
//解析器工厂类
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//解析器类,通过解析器工厂类来获得
DocumentBUilder dbBuilder = dbFactory.newDocumentBuilder();
//文档树模型,将要解析的 xml 文件读入 DOM 解析器
//Document 对象代表了一个 xml 文档的模型树,所有的其他 Node 都 以一定的顺序包含在 Document 对象之内, 排列成一个树状结构,以后对 xml 文档的所有操作都与解析器无关
Document doc = dbBuilder.parse(context.getAssets().open("person.xml"));
//节点列表类 NodeList ,代表一个包含一个或多个 Node 的列表,可看作数组,有以下两个方法
item(int index); //返回集合的第 index 各 Node 项
getLength(); //列表的节点数
//节点类 Node ,DOM 中最基本的对象,代表文档树中的抽象节点,很少会直接使用的,通常是调用它的子对象 Element、Attr、Text 等
//元素类 Element ,Node 最主要的子对象,在元素中可以包含属性,因此有取属性的方法:getAttribute()获得属性值, getTagName() 获得元素的名称
//属性类 Attr , 代表某个元素的属性,Attr 实现 Node 接口,虽然 Attr 是包含在 Element 中的,但并不能将其看作是 Element 的子对象, 因为 Attr 并不是 DOM 树的一部分
代码栗子:
DOM 解析类:DOMHelper.java
public class DOMHelper {
public static ArrayList<Person> queryXML(Context context){
ArrayList<Person> persons = new ArrayList<Person>();
try {
//获得 DOM 解析器工厂实例
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//从 DOM 工厂获得 DOM 解析器
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
//把要解析的 xml 文件读入 DOM 解析器
Document doc = dbBuilder.parse(context.getAssets().open("person.xml"));
Log.d("处理该文档的 DomImplementation 对象=" + doc.getImplementation());
//得到文档总名称为 person 的元素的节点列表
NodeList nList = doc.getElementsByTagName("person");
//遍历该集合,显示集合中的元素以及子元素的名字
for (int i = 0; i < nList.getLength(); i++) {
//先从 Person 元素开始解析
Element personElement = (Element) nList.item(i);
Person person = new Person();
person.setId(Integer.valueOf(personElement.getAttribute("id")));
//获取 person 下的 name 和 age 的 Node 集合
NodeList childNodeList = personElement.getChildNodes();
for(int j = 0; j < childNodeList.getLength(); j++) {
NOde childNOde = childNodeList.item(j);
//判断子 Node 类型是否为元素 Node
if (childeNode.getNodeType() == Node.ELEMENT_NODE) {
Element childElement = (Element) childNode;
if("name".equals(childElement.getNodeName()))
person.setName(childElement.getFirstChild().getNodeValue());
else if ("age".equals(childElement.getNodeName()))
person.setAge(Integer.valueOf(childElement.getFirstChild().getNodeValue();
}
}
persons.add(person);
}
}catch (Exception e) {
e.printStackTrace();
}
return persons;
}
}
3.PULL:
- 处理形式:PULL 的编码比较简单,只需处理开始和结束事件,通常是使用 switch 语句,根据事件的 不同类型,匹配不同的处理方式,有5种事件:START_DOCUMENT , START_TAG , TEXT , END_TAG , END_DOCUMENT 。 当某个元素开始时,可以调用 parser.nextText() 从 xml 文档中提取所有字符数据,当解析到一个文档结束时,自动生成 EndDocument 事件。在 PULL 解析过程中返回的是数字,且我们需要自己获取产生的事件然后做相应的操作,而不像 SAX 那样由处理器触发一种事件的方法然后执行我们的代码,读取到 xml 的声明返回 START_DOCUMENT,结束返回 END_DOCUMENT,开始标签返回 START_TAG,结束标签返回 END_TAG, 文本返回 TEXT。
- 优点:和 SAX 差不多,但代码写起来比 SAX 简单些,适合移动设备使用
- 用法:
使用 PULL 解析 xml:
第一步:获得一个 XmlPullParser 类的引用,有两种方法
//通过 xml 解析工厂获得实例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
//直接获得实例
XmlPullParser parser = Xml.newPullParser();
第二步:为 parser 解析器对象提供 xml 流与编码格式
parser.setInput(xml, "UTF-8");
第三步:获得时间的类型
int eventType = parser.getEvent();
第四步:用 switch 对不同的事件类型进行处理
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType){
//START_DOCUMENT,开始读文档是触发,在这里完成一些初始化操作
case XmlPullParser.START_DOCUMENT:
persons = new ArrayList<Person>();
break;
//START_TAG,开始读标签,通过 parser 的 getName()方法获得标签名进行比较
//使用 getAttributeValue(index) 可取出属性的值
case XmlPullParser.START_TAG:
if("person".equals(parser.getName())) {
person = new Person();
//取出属性值
int id = Integer.parseInt(parser.getAttributeValue(0)));
person.setId(id);
}else if ("name".equals(parser.getName())) {
String name = parser.nextText(); //获取该节点的内容
person.setName(name);
}else if ("age".equals(parser.getName())) {
int age = Integer.parseInt(parser.nextText());
person.setAge(age);
}
break;
case XmlPullParser.END_TAG:
if("person".equals(parser.getName())) {
persons.add(person);
person = null;
}
break;
}
eventType = parser.next(); //循环解析下一个元素
}
完整代码:
public static ArrayList<Person> getPersons(InputStream xml) throws Exception {
ArrayList<Person> persons = null;
Person person = null;
//创建一个 xml 解析的工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//获得 xml 解析类的引用
XmlPullParser parser = factory.newPullParser();
//为 xml 解析器提供 xml 输入流和编码格式
parser.setInput(xml, "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:
if("person".equals(parser.getName())) {
person = new Person();
//取出属性值
int id = Integer.parseInt(parser.getAttributeValue(0));
person.setId(id);
}else if ("name".equals(parser.getName())) {
String name = parser.nextText();
person.setName(name);
}else if ("age".equals(parser.getName())) {
int age = Integer.parseInt(parser.nextText());
person.setAge(age);
}
break;
case XmlPullParser.END_TAG:
if ("person".equals(parser.getName())) {
persons.add(person);
person = null;
}
break;
}
eventType = parser.next();
}
return persons;
}
使用 PULL 生成 xml 文件:
第一步:创建 XmlSerializer(xml 序列化类)的实例
XmlSerializer serializer = Xml.newSerializer();
第二步:为 XmlSerializer 设置输出流与编码格式
serializer.setOutput(out, "UTF-8");
第三步:为 XmlSerializer 设置 xml 的编码格式
serializer.startDocument("UTF-8", true);
第四步:设置根元素
serializer.startTag(null, "persons");
第五步:使用增强 for 循环遍历 persons 集合中的所有元素,同时依次写入标签与属性
for (Person p : persons) {
serializer.startTag(null, "person");
serializer.attribute(null, "id", p.getId() + "");
serializer.startTag(null, "name");
serializer.text(p.getName());
serializer.endTag(null, "name");
serializer.startTag(null, "age");
serializer.text(p.getAge() + "");
serializer.endTag(null, "age");
serializer.endTag(null, "person");
}
第六步:设置根完结元素
serializer.endTag(null, "persons");
第七步:结束文档编写
serializer.endDocument();
第八步:调用 flush() 将内存中的数据写入文件中并关闭输出流
out.flush();
out.close();
完整代码:
public static void save(List<Person> persons, OutputStream out) throws Exception {
XmlSerializer serializer = Xml.newSerializer();
serializer,.setOutput(out, "UTF-8");
serializer.startDocument("UTF-8", true);
serializer.startTag(null, "persons");
for (person p: persons) {
serializer.startTag(null, "person");
serializer.attribute(null, "id", p.getId() + "");
serializer.startTag(null, "name");
serializer.text(p.getName());
serializer.endTag(null, "name");
serializer.startTag(null, "age");
serializer.text(p.getAge() + "");
serializer.endTag(null, "age");
serializer.endTag(null, "person");
}
serializer.endTag(null, "persons");
serializer.endDocument();
out.flush();
out.close();
}