转载请标明出处:【Gypsophila 的博客】 http://blog.csdn.net/astro_gypsophila/article/details/60509425
三种解析 XML 方法
- DOM 解析 XML
- SAX 解析 XML
- Pull 解析XML
示例的 XML 文件
<?xml version="1.0" encoding="UTF-8"?>
<students show="1">
<student id="1">
<name>
<firstName>张</firstName>
<lastName>三</lastName>
</name>
<age>20</age>
</student>
<student id="2">
<name>
<firstName>李</firstName>
<lastName>四</lastName>
</name>
<age>22</age>
</student>
<student id="3">
<name>
<firstName>王</firstName>
<lastName>五</lastName>
</name>
<age>22</age>
</student>
</students>
涉及的 Java 类:
public class StudentBean {
private int id;
private String firstName;
private String lastName;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "StudentBean{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}
public class StudentPlusBean {
//是否展示列表
private int show;
private List<StudentBean> students = new ArrayList<>();
public int getShow() {
return show;
}
public void setShow(int show) {
this.show = show;
}
public List<StudentBean> getStudents() {
return students;
}
@Override
public String toString() {
return "StudentPlusBean{" +
"show=" + show +
", students=" + students +
'}';
}
}
DOM 解析 XML
DOM 解析 XML 文件会将整个文件以文档树的形式存放到内存中,然后可以使用 DOM API 遍历 XML 树。因为加载 XML 文件所有内容到内存,所以内存消耗比较大,对于移动设备来说,建议采用 SAX 解析或者是 XML 文件内容比较小再采用 DOM。
public class DOMParseHelper {
public static void parse(Context context) {
StudentPlusBean studentPlusBean = new StudentPlusBean();
try {
// 获取工厂实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 获取 DOM 解析器
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = context.getAssets().open("config_students.xml");
// 调用 DOM 解析器解析整份文档,获取文档对象,document
Document document = builder.parse(is);
// 根节点 students
Node root = document.getFirstChild();
Element rootElement = (Element) root;
studentPlusBean.setShow(Integer.valueOf(rootElement.getAttribute("show")));
NodeList studentNodeList = document.getElementsByTagName("student");
for (int i = 0; i < studentNodeList.getLength(); i++) {
// 根据 tagname 获取的全是元素节点
StudentBean student = new StudentBean();
Element studentElement = (Element) studentNodeList.item(i);
// 解析属性 id
student.setId(Integer.valueOf(studentElement.getAttribute("id")));
// 方式一 (更加直观) // 解析 name
// 因为很确定通过 tagName 方式查找一个 student 元素下,只会找到一个对应节点,且该节点为元素节点
// 所以直接 item(0) 且转型为 Element,剩下的查找也是同理
Element nameElement = (Element) studentElement.getElementsByTagName("name").item(0);
Element firstName = (Element) nameElement.getElementsByTagName("firstName").item(0);
student.setFirstName(firstName.getFirstChild().getNodeValue());
Element lastName = (Element) nameElement.getElementsByTagName("lastName").item(0);
student.setLastName(lastName.getFirstChild().getNodeValue());
// 解析 age
Element ageElement = (Element) studentElement.getElementsByTagName("age").item(0);
student.setAge(Integer.valueOf(ageElement.getFirstChild().getNodeValue()));
// 方式二
/*
for (int j = 0; j < studentElement.getChildNodes().getLength(); j++) {
Node studentContentNode = studentElement.getChildNodes().item(j);
if (studentContentNode.getNodeType() == Node.ELEMENT_NODE) {
Element studentContentElement = (Element) studentContentNode;
if (studentContentElement.getNodeName().equals("name")) {
// 解析 name 部分
NodeList nameNodeList = studentContentElement.getChildNodes();
for (int k = 0; k < nameNodeList.getLength(); k++) {
Node nameDetailNode = nameNodeList.item(k);
if (nameDetailNode.getNodeType() == Node.ELEMENT_NODE) {
Element nameDetailElement = (Element) nameDetailNode;
if (nameDetailElement.getNodeName().equals("firstName")) {
student.setFirstName(nameDetailElement.getFirstChild().getNodeValue());
} else if (nameDetailElement.getNodeName().equals("lastName")) {
student.setLastName(nameDetailElement.getFirstChild().getNodeValue());
}
}
}
} else if (studentContentElement.getNodeName().equals("age")) {
// 解析 age
student.setAge(Integer.valueOf(studentContentElement.getFirstChild().getNodeValue()));
}
}
}
*/
System.out.println("student = " + student);
studentPlusBean.getStudents().add(student);
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
}
说明:这边解析的写法方式都是因人而异,这边所谓的方式一和二不过想突出 Node 和 Element 的差别。方式二是按照文档树的层次结构,所以有很多层的循环,都是为了遍历每层元素节点,而所遍历到的节点并不都是元素节点。
DOM 解析方式至少需注意元素和节点的区别
SAX 解析 XML
Simple API for XML(简称SAX)是个循序存取XML的解析器API。
SAX 就像读取流一样做着解析工作,运行起来为单向。即无法再访问读取过的资料,除非再从头开始读取一遍。采用事件驱动,不需要解析整份文档,而是在读取文档过程中,判断读取到的字符是否符合 XML 某部分,符合就出发事件调用我们的所写回调函数。它是一个解析速度快且占用内存少的 XML 解析器,非常适合用于 Android 等移动设备。
用法:继承 DefaultHandler ,然后可选择性重写以下几个方法。
// 继承 DefaultHandler 并且重写五个方法
public class SAXParseHelper extends DefaultHandler {
private StudentBean student;
private String tagName;
private StudentPlusBean studentPlusBean;
@Override
public void startDocument() throws SAXException {
super.startDocument();
// 判断读取到文档开始时触发,可以在此做初始化操作
studentPlusBean = new StudentPlusBean();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
// 读取到标签开始,这里可以获取所有属性
if (localName.equals("students")) {
studentPlusBean.setShow(Integer.valueOf(attributes.getValue("show")));
} else if (localName.equals("student")) {
student = new StudentBean();
student.setId(Integer.valueOf(attributes.getValue("id")));
}
tagName = localName;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
// 读取到的内容部分
if (tagName != null) {
String data = new String(ch, start, length);
if (tagName.equals("firstName")) {
student.setFirstName(data);
} else if (tagName.equals("lastName")) {
student.setLastName(data);
} else if (tagName.equals("age")) {
student.setAge(Integer.valueOf(data));
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
// 对应标签结束
if (localName.equals("student")) {
studentPlusBean.getStudents().add(student);
System.out.println("student = " + student);
studentPlusBean.getStudents().add(student);
student = null;
}
tagName = null;
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
//文档结束部分
}
}
// 外部调用方式
private void parseXMLBySAX() {
try {
//获取文件资源建立输入流对象
InputStream is = getAssets().open("config_students.xml");
//创建解析处理器
SAXParseHelper helper = new SAXParseHelper();
//得到SAX解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//创建SAX解析器
SAXParser parser = factory.newSAXParser();
parser.parse(is, helper);
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
说明:SAX 较为高效!由于在解析过程中没有层级深入的感觉,所有的相关数据获取都是写在同一级的判断中,所以随着 XML 结构复杂,写法应该也越难理清,一堆的 if else 判断。(但我喜欢这种 SAX 和Pull,感觉好简便!:P)
PULL 解析 XML
Pull 解析器是 Android 内置用来解析 XML 文件。它的使用与 SAX 相似,采用事件驱动来完成 XML 解析。
private void parseXMLByPULL() {
StudentPlusBean studentPlusBean = null;
StudentBean student = null;
try {
//1.直接获得实例
//XmlPullParser parser = Xml.newPullParser();
//2.使用工厂获得实例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(getAssets().open("config_students.xml"), "utf-8");
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
studentPlusBean = new StudentPlusBean();
break;
case XmlPullParser.START_TAG:
if ("students".equals(parser.getName())) {
studentPlusBean.setShow(Integer.valueOf(parser.getAttributeValue(0)));
} else if ("student".equals(parser.getName())) {
student = new StudentBean();
// 取属性值方式1.明确所取属性的 index 时
// student.setId(Integer.valueOf(parser.getAttributeValue(0)));
// 取属性值方式2.直接根据属性的名称取出值
student.setId(Integer.valueOf(parser.getAttributeValue(null,"id")));
} else if ("firstName".equals(parser.getName())) {
student.setFirstName(parser.nextText());
} else if ("lastName".equals(parser.getName())) {
student.setLastName(parser.nextText());
}else if ("age".equals(parser.getName())) {
student.setAge(Integer.valueOf(parser.nextText()));
}
break;
case XmlPullParser.END_TAG:
if ("student".equals(parser.getName())) {
System.out.println("student = " + student);
studentPlusBean.getStudents().add(student);
student = null;
}
break;
}
// 下一个解析事件
eventType = parser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
说明:是否感觉 SAX 与 Pull 方式很接近?都是对读取的标签事件时,做具体的逻辑处理,一般就是取出它们的属性或者值。
注意:Pull 解析中遇到若元素中含有元素和节点值都需要解析,应该 attribute 先解析,然后获取节点值。
比如 name 中的 firstName 还多了个 show 属性
...
<firstName show="1">李</firstName>
<lastName>四</lastName>
...
应当注意获取解析属性和节点值的顺序,应当是先获取属性值,然后获取节点值。否则抛出 java.lang.IndexOutOfBoundsException
// 正确顺序
else if ("firstName".equals(parser.getName())) {
System.out.println("show = "+parser.getAttributeValue(0));
System.out.println("firstName = "+parser.nextText());
}
// 错误顺序
System.out.println("firstName = "+parser.nextText());
System.out.println("show = "+parser.getAttributeValue(0));
点击 parser.nextText()
方法中查看注释,就可明白当解析到 eventType == TEXT
if(eventType == TEXT) {
String result = getText();
eventType = next();
if(eventType != END_TAG) {
throw new XmlPullParserException(
"event TEXT it must be immediately followed by END_TAG", this, null);
}
return result;
}
执行了 next()
,解析会走到 END_TAG 事件,即此时走到了</firstName>
,这边肯定是没有 show
属性的,所以parser.getAttributeValue(0)
取不到第一个属性,发生越界。