[Android笔记]Xml的解析
本文学习一下目前Android平台对XML解析的三种解析方式: DOM、 SAX、 Pull。
需要解析的XML测试文档如下:
<?xml version="1.0" encoding="gb2312"?>
<Book>
<BookAuthorte>成功</BookAuthorte>
<BookNullCode>0</BookNullCode>
<BookDate>2018-02-26</BookDate>
<BookInfo id="1">
<BookName>第一行代码</BookName>
<BookAuthor>郭霖</BookAuthor>
</BookInfo>
<BookInfo id="2">
<BookName>深入殂Android自动化测试</BookName>
<BookAuthor>许奔</BookAuthor>
</BookInfo>
<BookInfo id="3">
<BookName>Java并发编程的艺术</BookName>
<BookAuthor>方腾飞 魏鹏 程晓明</BookAuthor>
</BookInfo>
</Book>
Book信息的实体类如下:
public class BookInfo {
public static final String TAG = "BookInfo";
public static final String ELEMENT_TAG_BOOKINFO = "BookInfo";
public static final String[] ELEMENT_TAG_LIST = {
"id",
"BookName",
"BookAuthor"
};
private String mId;
private String mBookName;
private String mBookAuthor;
public BookInfo() {
super();
}
public BookInfo(String[] result) {
int i = 0;
mId = result[i++];
mBookName = result[i++];
mBookAuthor = result[i++];
}
public void setmId(String mId) {
this.mId = mId;
}
public void setmBookName(String mBookName) {
this.mBookName = mBookName;
}
public void setmBookAuthor(String mBookAuthor) {
this.mBookAuthor = mBookAuthor;
}
@Override
public String toString() {
return "[Id: "+ mId+ ",BookName: " + mBookName + "| BookAuthor:" + mBookAuthor+ "]\n";
}
}
1. DOM解析器
DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。
Android完全支持DOM 解析。利用DOM中的对象,可以对XML文档进行读取、搜索、修改、添加和删除等操作。
DOM的工作原理:使用DOM对XML文件进行操作时,首先要解析文件,将文件分为独立的元素、属性和注释等,然后以节点树的形式在内存中对XML文件进行表示,就可以通过节点树访问文档的内容,并根据需要修改文档——这就是DOM的工作原理。
DOM实现时首先为XML文档的解析定义一组接口,解析器读入整个文档,然后构造一个驻留内存的树结构,这样代码就可以使用DOM接口来操作整个树结构。
由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。 当然,如果XML文件的内容比较小,采用DOM是可行的。
常用的DoM接口和类:
Document:该接口定义分析并创建DOM文档的一系列方法,它是文档树的根,是操作DOM的基础。
Element:该接口继承Node接口,提供了获取、修改XML元素名字和属性的方法。
Node:该接口提供处理并获取节点和子节点值的方法。
NodeList:提供获得节点个数和当前节点的方法。这样就可以迭代地访问各个节点。
DOMParser:该类是Apache的Xerces中的DOM解析器类,可直接解析XML文件。
通过DOM来解析XML的步骤是:
- 构建DocumentBuilderFactory对象:DocumentBuilderFactory.newInstance();
- 通过DocumentBuilderFactory对象获取DocumentBuilder对象(factory.newDocumentBuilder());
- 使用构建好的DocumentBuilder对象的 parse(InputStream)方法进行解析xml, 返回一Document对象,该对象就代码我们整个XML文档;
- 然后我们就可以获得其下面各个元素节点(Element), 而每个元素节点可能又有多个属性(Attribute), 根据每个元素节点,我们可遍历该元素节点下面的子节点等。
示例代码:
public class DomParserUtils {
//InputStream is = new FileInputStream(new File("D:/test1.xml"));
public static List<BookInfo> parserXML(String xmlString) {
InputStream input;
//TODO: 需要注意XML是使用什么格式,如: <?xml version="1.0" encoding="gb2312"?>
try {
input = new ByteArrayInputStream(xmlString.getBytes("gb2312"));
return parserXML(input);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static List<BookInfo> parserXML(InputStream inputStream) {
ArrayList<BookInfo> bookInfoList = new ArrayList<>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
Document document = documentBuilder.parse(inputStream);
Element root = document.getDocumentElement();
//TODO: 直接找需要的Elements
NodeList BookInfoNodes = root.getElementsByTagName(BookInfo.ELEMENT_TAG_BOOKINFO);
//TODO: 方式一:按顺序进行全部加载
for(int i = 0; i < BookInfoNodes.getLength(); i++) {
Element infoNode = (Element) BookInfoNodes.item(i);
String[] parameters = new String[BookInfo.ELEMENT_TAG_LIST.length];
//得到id的属性值
parameters[0] = infoNode.getAttribute("id");
//顺序获取各个属性的值
for(int j = 1; j< BookInfo.ELEMENT_TAG_LIST.length; j++){
NodeList nodes = infoNode.getElementsByTagName(BookInfo.ELEMENT_TAG_LIST[j]);
if(nodes.getLength()>0){
Element e = (Element) nodes.item(0);
Node node = e.getFirstChild();
if (node != null) {
parameters[j] = node.getNodeValue();
}
}
}
BookInfo bookInfo = new BookInfo(parameters);
bookInfoList.add(bookInfo);
}
//TODO:方式二:按需求进行选择性数据加载
for(int i = 0; i < BookInfoNodes.getLength(); i++) {
Element infoNode = (Element) BookInfoNodes.item(i);
String[] parameters = new String[BookInfo.ELEMENT_TAG_LIST.length];
//得到id的属性值
parameters[0] = infoNode.getAttribute("id");
NodeList childNodesList = infoNode.getChildNodes();
for(int j = 0; j< childNodesList.getLength(); j++){
//需要加一定的Null检查条件 ,这里略过
if (childNodesList.item(j).getNodeType() == Document.ELEMENT_NODE) {
//只需要书名
if(BookInfo.ELEMENT_TAG_LIST[1].equals(childNodesList.item(j).getNodeName())){
parameters[1] = childNodesList.item(j).getFirstChild().getNodeValue();
}
}
}
BookInfo bookInfo = new BookInfo(parameters);
bookInfoList.add(bookInfo);
}
inputStream.close();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bookInfoList;
}
}
通过DOM解析xml的好处就是,我们可以随时访问到某个节点的相邻节点,并且对xml文档的插入也非常的方便,不好的地方就是,其会将整个xml文档加载到内存中,这样会大大的占用我们的内存资源,对于手机来说,内存资源是非常非常宝贵的,所以在手机当中,通过DOM这种方式来解析xml是用的比较少的。
2.SAX解析器
SAX(Simple API for XML)解析器是一种基于事件的解析器,事件驱动的流式解析方式是,从文件的开始顺序解析到文档的结束,不可暂停或倒退。它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。
SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。
SAX的工作原理:SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
常用的SAX接口和类:
Attrbutes:用于得到属性的个数、名字和值。
ContentHandler:定义与文档本身关联的事件(例如,开始和结束标记)。大多数应用程序都注册这些事件。
DTDHandler:定义与DTD关联的事件。它没有定义足够的事件来完整地报告DTD。如果需要对DTD进行语法分析,请使用可选的DeclHandler。
DeclHandler是SAX的扩展。不是所有的语法分析器都支持它。
EntityResolver:定义与装入实体关联的事件。只有少数几个应用程序注册这些事件。
ErrorHandler:定义错误事件。许多应用程序注册这些事件以便用它们自己的方式报错。
DefaultHandler:它提供了这些接LI的缺省实现。在大多数情况下,为应用程序扩展DefaultHandler并覆盖相关的方法要比直接实现一个接口更容易。
ContentHandler的常用接口方法:
startDocument()
当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。
endDocument()
和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。
startElement(String namespaceURI, String localName, String qName, Attributes atts)
当读到一个开始标签的时候,会触发这个方法。namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。通过atts可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理,当遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中,所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,都需要你的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。
endElement(String uri, String localName, String name)
这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。
characters(char[] ch, int start, int length)
这个方法用来处理在XML文件中读到的内容,第一个参数用于存放文件的内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就可以获取内容。
通过SAX来解析XML的步骤是:
- 构建SAXParserFactory对象、SAXParser对象以便获取XMLReader对象;
- 实现自己的 Handler(继承DefaultHandler); 在自己的Handler实现获取需要的xml数据.
- 实例InputSource对象,对InputSource对象进行设置自己实现Handler进行设置,xml的格式、及StringReader对象,
- 将InputSource对象传给XMLReader的parse(inputSource). 这样解析就会开始进行;
- 再通过自己的Handler保存的数据进行读取结果;
示例代码:
public class SaxParserUtil {
public static List<BookInfo> parserXML(String xmlString)
{
SaxHandler myHandler = null;
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
// 实例化我们的MyHandler类
myHandler = new SaxHandler();
// 根据我们自定义的Handler来解析xml文档
XMLReader reader = parser.getXMLReader();
reader.setContentHandler(myHandler);
InputSource inputSource = new InputSource();
//TODO: 需要注意XML是使用什么格式,如: <?xml version="1.0" encoding="gb2312"?>
inputSource.setEncoding("gb2312");
inputSource.setCharacterStream(new StringReader(xmlString));
reader.parse(inputSource);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (myHandler == null)
return null;
return myHandler.getBooks();
}
static class SaxHandler extends DefaultHandler {
private List<BookInfo> bookInfos;
private BookInfo bookInfo;
// 存放当前解析到的标签名字
private String currentTag;
// 存放当前解析到的标签的文本值
private String currentValue;
public List<BookInfo> getBooks() {
return bookInfos;
}
/**
* 当解析到文档开始时的回调方法
* */
@Override
public void startDocument() throws SAXException {
super.startDocument();
bookInfos = new ArrayList<>();
}
/**
* 当解析到xml的标签时的回调方法
* */
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (BookInfo.ELEMENT_TAG_BOOKINFO.equals(qName)) {
bookInfo = new BookInfo();
// 得到当前元素的属性值
for(int i = 0; i < attributes.getLength(); i++) {
if ("id".equals(attributes.getQName(i))) {
bookInfo.setmId(attributes.getValue(i));
}
}
}
// 设置当前的标签名
currentTag = qName;
}
/**
* 当解析到xml的文本内容时的回调方法
* */
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 得到当前的文本内容
currentValue = new String(ch,start, length);
// 当currentValue不为null、""以及换行时
if(currentValue != null && !"".equals(currentValue) && !"\n".equals(currentValue))
{
// 判断当前的currentTag是哪个标签
if("BookName".equals(currentTag))
{
bookInfo.setmBookName(currentValue);
}
else if("BookAuthor".equals(currentTag))
{
bookInfo.setmBookAuthor(currentValue);
}
}
// 清空currentTag和currentValue
currentTag = null;
currentValue = null;
}
/**
* 当解析到标签的结束时的回调方法
* */
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(BookInfo.ELEMENT_TAG_BOOKINFO.equals(qName))
{
bookInfos.add(bookInfo);
bookInfo = null;
}
}
}
}
3.Pull解析器
PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中返回的是数字,且我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。
Pull解析器和SAX解析器的区别:
Pull解析器和SAX解析器虽有区别但也有相似性。他们的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能控制事件的处理主动结束;
而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件,正因为是主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。这是他们主要的区别。
而他们的相似性在运行方式上,Pull解析器也提供了类似SAX的事件(开始文档START_DOCUMENT和结束文档END_DOCUMENT,开始元素START_TAG和结束元素END_TAG,遇到元素内容TEXT等),但需要调用next() 方法提取它们(主动提取事件)。
PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器,Android官方推荐开发者们使用Pull解析技术。Pull解析技术是第三方开发的开源技术,它同样可以应用于JavaSE开发。
PULL 的工作原理:XML pull提供了开始元素和结束元素。当某个元素开始时,我们可以调用parser.nextText从XML文档中提取所有字符数据。当解释到一个文档结束时,自动生成EndDocument事件。
常用的XML pull的接口和类:
XmlPullParser:XML pull解析器是一个在XMLPULL VlAP1中提供了定义解析功能的接口。
XmlSerializer:它是一个接口,定义了XML信息集的序列。
XmlPullParserFactory:这个类用于在XMPULL V1 API中创建XML Pull解析器。
XmlPullParserException:抛出单一的XML pull解析器相关的错误。
示例代码:
public class PullParserUtil {
public static List<BookInfo> parserXML(String xmlString) {
List<BookInfo> bookInfos = null ;
BookInfo bookInfo = null ;
try {
// 创建XmlPullParserFactory解析工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
// 通过XmlPullParserFactory工厂类实例化一个XmlPullParser解析类
XmlPullParser parser = factory.newPullParser();
InputStream inputStream = new ByteArrayInputStream(xmlString.getBytes("gb2312"));
// 根据指定的编码来解析xml文档
parser.setInput(inputStream,"gb2312");
// 得到当前的事件类型
int eventType = parser.getEventType();
// 只要没有解析到xml的文档结束,就一直解析
while(eventType != XmlPullParser.END_DOCUMENT)
{
switch (eventType)
{
// 解析到文档开始的时候
case XmlPullParser.START_DOCUMENT:
bookInfos = new ArrayList<>();
break;
// 解析到xml标签的时候
case XmlPullParser.START_TAG:
if(BookInfo.ELEMENT_TAG_BOOKINFO.equals(parser.getName()))
{
bookInfo = new BookInfo();
// 得到person元素的第一个属性,也就是ID
bookInfo.setmId(parser.getAttributeValue(0));
}
else if("BookName".equals(parser.getName()))
{
// 如果是name元素,则通过nextText()方法得到元素的值
bookInfo.setmBookName(parser.nextText());
}
else if("BookAuthor".equals(parser.getName()))
{
bookInfo.setmBookAuthor(parser.nextText());
}
break;
// 解析到xml标签结束的时候
case XmlPullParser.END_TAG:
if(BookInfo.ELEMENT_TAG_BOOKINFO.equals(parser.getName()))
{
bookInfos.add(bookInfo);
bookInfo = null;
}
break;
}
// 通过next()方法触发下一个事件
eventType = parser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bookInfos;
}
}
测试的Acitity代码:
public class XMLParserActivity extends Activity {
public static final String TAG = "XMLParserActivity";
@Bind(R.id.dom)
Button dom;
@Bind(R.id.sax)
Button sax;
@Bind(R.id.pull)
Button pull;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.xml_parser_layout);
ButterKnife.bind(this);
}
@OnClick(R.id.dom)
public void onClickDOM(View view) {
long starTime = System.currentTimeMillis();
Log.d(TAG, "dom: " + DomParserUtils.parserXML(xmlString).toString());
Log.d(TAG, "dom 解析花费了: " + String.valueOf(System.currentTimeMillis() - starTime));
}
@OnClick(R.id.sax)
public void onClickSAX(View view) {
long starTime = System.currentTimeMillis();
Log.d(TAG, "sax: " + SaxParserUtil.parserXML(xmlString).toString());
Log.d(TAG, "sax 解析花费了: " + String.valueOf(System.currentTimeMillis() - starTime));
}
@OnClick(R.id.pull)
public void onClickPull(View view) {
long starTime = System.currentTimeMillis();
Log.d(TAG, "pull: " + PullParserUtil.parserXML(xmlString).toString());
Log.d(TAG, "pull 解析花费了: " + String.valueOf(System.currentTimeMillis() - starTime));
}
String xmlString = "<?xml version=\"1.0\" encoding=\"gb2312\"?>\n" +
"<Book>\n" +
" <BookAuthorte>成功</BookAuthorte>\n" +
" <BookNullCode>0</BookNullCode>\n" +
" <BookDate>2018-02-26</BookDate>\n" +
" <BookInfo id=\"1\">\n" +
" <BookName>第一行代码</BookName>\n" +
" <BookAuthor>郭霖</BookAuthor>\n" +
" </BookInfo>\n" +
" <BookInfo id=\"2\">\n" +
" <BookName>深入殂Android自动化测试</BookName>\n" +
" <BookAuthor>许奔</BookAuthor>\n" +
" </BookInfo>\n" +
" <BookInfo id=\"3\">\n" +
" <BookName>Java并发编程的艺术</BookName>\n" +
" <BookAuthor>方腾飞 魏鹏 程晓明</BookAuthor>\n" +
" </BookInfo>\n" +
"</Book>";
}
输出结果:
D/XMLParserActivity: dom: [[Id: 1,BookName: 第一行代码| BookAuthor:郭霖]
, [Id: 2,BookName: 深入殂Android自动化测试| BookAuthor:许奔]
, [Id: 3,BookName: Java并发编程的艺术| BookAuthor:方腾飞 魏鹏 程晓明]
, [Id: 1,BookName: 第一行代码| BookAuthor:null]
, [Id: 2,BookName: 深入殂Android自动化测试| BookAuthor:null]
, [Id: 3,BookName: Java并发编程的艺术| BookAuthor:null]
]
D/XMLParserActivity: dom 解析花费了: 2
D/XMLParserActivity: sax: [[Id: 1,BookName: 第一行代码| BookAuthor:郭霖]
, [Id: 2,BookName: 深入殂Android自动化测试| BookAuthor:许奔]
, [Id: 3,BookName: Java并发编程的艺术| BookAuthor:方腾飞 魏鹏 程晓明]
]
D/XMLParserActivity: sax 解析花费了: 2
D/XMLParserActivity: pull: [[Id: 1,BookName: 第一行代码| BookAuthor:郭霖]
, [Id: 2,BookName: 深入殂Android自动化测试| BookAuthor:许奔]
, [Id: 3,BookName: Java并发编程的艺术| BookAuthor:方腾飞 魏鹏 程晓明]
]
D/XMLParserActivity: pull 解析花费了: 1
由于XML的数据过少, 解析时间是一样的。
总结:dom方式解析xml,比较简单,并可以访问兄弟元素,但是需要将整个xml文档加载到内存中,对于android设备来说,不推荐使用dom的方式解析xml。sax和pull都是基于事件驱动的xml解析器,在解析xml时并不会加载整个的xml文档,占用内存较少,因此在android开发中建议使用sax或者pull来解析xml文档。
本文关于三种解析器的描述及部分代码都直接转载于下面的大神的文章:
[1]: https://www.cnblogs.com/xiaoluo501395377/p/3444744.html
[2]: http://www.cnblogs.com/weixing/p/3243366.html