市面上有许多API提供商,JSON和XML成为了主要的信息载体,数据解析是相当重要。
1.JSON,由字符串根据特定的格式形成的,传输和解析的速度都非常快。
2.XML,标签式文档,易于拓展,性能没JSON好,但处理大量的数据或者复杂的数据比JSON好使。
本帖用实例代码详解四种数据解析方式,两种是XML的,两种是JSON的。至于JSON和XML的写法规则,大家都懂的,没必要写了。
JSON:Android自带的JSON解析,Gson谷歌的东西。
XML:SAXPaser,XmlPullPaser,这两种性能较好,适合移动设备。
先看看数据结构和需求,需求是,找出这个school里B班的所有学生的信息。
school
classA
学生信息,id=101,classmate=Ben
.............................................................
.............................................................
classB
.............................................................
.............................................................
. ............................................................
classC
.............................................................
.............................................................
一、先来JSON,看看数据如下图:
{
"school":{
"classA":[
{
"id":"101",
"classmate":"Ben"
},
{
"id":"102",
"classmate":"Bell"
},
{
"id":"103",
"classmate":"Bean"
}
],
"classB":[
{
"id":"201",
"classmate":"Peter"
},
{
"id":"202",
"classmate":"Paul"
},
{
"id":"203",
"classmate":"Po"
}
],
"classC":[
{
"id":"301",
"text":"Sarah"
},
{
"id":"302",
"text":"Steven"
},
{
"id":"303",
"text":"Sam"
}
]
}
}
(1)Gson
Gson有个好处,可以根据JavaBean的结构。
先根据上述数据写个结构先,GsonBean.java。
规则是JSONOject的话用类对象(除了String这个类,还可以自定义或其他的),JSONArray的话用List集合(集合里的类除了String也可以放自定义或其他的)。
第一层,school的内容是个JSONOject,用一个自定义School类定义。
第二层,school的内容这个JSONOject里面放的是三个班的信息,自定义School类放的是定义三个班的信息。
第三层,三个class放的的内容都是一个JSONArray,用List集合。
第四层,上一层的List集合里,每个同学的信息是一个JSONOject,我没把同学的信息的JavaBean写在GsonBean.java,按照上述的规律,写在另外的JavaBean文件ClassmateBean.java。
GsonBean.java
package ljy.com.jsonandxmldemo;
import java.util.List;
/**
* Created by Administrator on 2016/4/21.
*/
public class GsonBean {
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public class School {
private List<ClassmateBeen> classA;
private List<ClassmateBeen> classB;
private List<ClassmateBeen> classC;
public List<ClassmateBeen> getClassA() {
return classA;
}
public void setClassA(List<ClassmateBeen> classA) {
this.classA = classA;
}
public List<ClassmateBeen> getClassB() {
return classB;
}
public void setClassB(List<ClassmateBeen> classB) {
this.classB = classB;
}
public List<ClassmateBeen> getClassC() {
return classC;
}
public void setClassC(List<ClassmateBeen> classC) {
this.classC = classC;
}
}
}
ClassmateBean.java
package ljy.com.jsonandxmldemo;
public class ClassmateBeen {
private String id;
private String classmate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassmate() {
return classmate;
}
public void setClassmate(String classmate) {
this.classmate = classmate;
}
@Override
public String toString() {
return "id="+id+",classmate="+classmate;
}
}
以上两个JavaBean是JSON解析的模型。下面是解析的主要代码,GsonRunnable.java里的代码片段。
第一步,new一个Gson对象。
第二步,new一个TypeToken<T>(){}的对象,T是泛类,传入的是解析结构模型,就是刚刚定义的GsonBean。
第三步,gson.from(参数1,参数2)方法就是解析json的。
参数1是一个Reader类型或字符串。我的json数据是InputStream流的形式,这里传入的是new一个BufferReader传进去,当然也可以传入String。
参数2是上一步的type(解析结构模型)。
最终这个方法返回一个GsonBean的类型,已经在根据我设定的解析结构模型解析完了,可以获取数据了。
第四步,返回一个GsonBean对象,看上述JavaBean结构,先获取school,然后才到classB,最后得到一个List<ClassmateBean>集合,遍历这个集合获取B班所有同学的信息。
StringBuffer sb=new StringBuffer();
Gson gson=new Gson();
Type type=new TypeToken<GsonBean>(){}.getType();
GsonBean bean=gson.fromJson(new BufferedReader(new InputStreamReader(is)),type);
List<ClassmateBeen> classB_list=bean.getSchool().getClassB();
(2)Android自带的JSON解析
Android自带的JSONOject和JSONArray,使用起来也是非常方便的。比起Gson的json解析就少了个数据模块化的过程而已。
同样的数据和需求,下面来分析下代码:
JsonRunnable.java 【流转为String】的方法。
传入的数据是流的形式,首先要有一个把流转为String的方法。这个没啥好说的,拷贝就行了。
private String Is2String(InputStream is) {
StringBuffer buff=new StringBuffer();
BufferedReader re=new BufferedReader(new InputStreamReader(is));
String temp=null;
try {
while ((temp=re.readLine())!=null){
buff.append(temp);
temp=null;
}
return buff.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
JsonRunnable.java 解析的片段
第一步,因为数据最外层的是一个JSONObject,所以new一个JSONObject对象,new JSONObject(参数),参数是一个字符串。
第二步,可以根据节点get相应的东西,先get一下school的,school的内容是一个JSONObject,所以使用方法getJSONObject("school"),参数是一个节点的名称。
第三步,classB的内容是一个JSONArray,所以使用方法getJSONArray("classB")。
第四步,最终遍历得到的这个classB的内容的JSONArray,就可以获取B班同学的信息。
</pre><pre name="code" class="java"> String json=Is2String(is);
if(json!=null){
try {
JSONArray arr=new JSONObject(json).getJSONObject("school").getJSONArray("classB");
JSONObject temp;
for(int i=0;i<arr.length();i++){
temp=arr.getJSONObject(i);
sb.append("id="+temp.getString("id")
+",classmate="+temp.getString("classmate")
+"\r\n");
}
} catch (JSONException e) {
e.printStackTrace();
}
<span > </span>}
json的解析并不难吧,来来去去都只是JSONObject和JSONArray。
二、再来XML,看看数据,我把用了上面JSON的数据,写成XML。
也可以通过[在线互换格式的数据的网站]转过来。
数据如下图:
根标签是school,二级标签是class,三级标签是同学的信息。
这里把同学的学号写在了classmate的标签内,是这个标签的属性,标签内的姓名,就是标签的内容。
<?xml version="1.0" encoding="utf-8"?>
<school>
<classA>
<classmate id="101">Ben</classmate>
<classmate id="102">Bell</classmate>
<classmate id="103">Bean</classmate>
</classA>
<classB>
<classmate id="201">Peter</classmate>
<classmate id="202">Paul</classmate>
<classmate id="203">Po</classmate>
</classB>
<classC>
<classmate id="301">Sarah</classmate>
<classmate id="302">Steven</classmate>
<classmate id="303">Sam</classmate>
</classC>
</school>
(1)SAXPaser
速度非常快的解析方式,但不能中途停止,要从头解析的最后。程序代码也做了一些优化,在解析过程如果找够了需要的数据,快速结束此次解析。
SaxHandler.java 内容看起来有点多,并不复杂,先继承DefaultHandler,然后重写的方法有那么几个:
startDocument(文档开始解析,就是读到根标签的开头),endDocument(文档结束解析,就是读到根标签的结尾),startElement(读到根标签里面的标签的开头),characters(执行完startElement,再回调此方法(开始和结束标签之间的内容),才执行endElement),endElement(读到根标签里面的标签的结尾)。
这里读取的步骤:
1.startDocument,读到根标签的时候,初始化一个用来储存同学信息的集合.
2.startElement,读取根标签内的标签的时候,做下判断:
(1)如果读到classB的标签的话,在全局变量isClassB上做个标记,再读到下面的characters和endElement方法的时候,如果不是classB标签内的话,不执行任何操作直接return,加快解析速度。
(2)如果标签读到已经是在classB标签内的话,再判断是否是climate标签,用全局变量isClassB_ele做标记,如果是的话,在startElement方法里,new一个Classmate的对象,上面解析json的时候用到的这个对象,可以复用,储存一个同学的学号信息,因为上述XML结构,同学的学号信息在classmate的开始标签内。然后到了characters方法,储存同一个同学的姓名,因为姓名在两个标签之间。最后到endElement方法,把储存了这名同学的所有信息的Classmate对象,加到第1步初始化的集合中。
(3)以此类推,把所有需要的信息获取完。
整个处理器部分就写完了,解析完成了一大半了。
SaxHandler.java
package ljy.com.jsonandxmldemo;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2016/4/20.
*/
public class SaxHandler extends DefaultHandler {
private List<ClassmateBeen> classb_list = null;
private ClassmateBeen classb = null;
private boolean isClassB = false;
private boolean isClassB_ele = false;
public List<ClassmateBeen> getData() {
return classb_list;
}
public SaxHandler() {
classb_list = new ArrayList<>();
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
// Log.d("SAX","uri="+uri.toString()+",localname="+localName
// +",attributes=["+attributes.getLocalName(0)+","+attributes.getQName(0)+","+attributes.getValue(0)+"]");
if (qName.equals("classB")) {
isClassB = true;
} else if (isClassB && qName.equals("classmate")) {
classb = new ClassmateBeen();
classb.setId(attributes.getValue(0));
isClassB_ele = true;
} else {
return;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (!isClassB) return;
super.characters(ch, start, length);
if (isClassB_ele)
classb.setClassmate(new String(ch, start, length));
isClassB_ele = false;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (!isClassB) return;
super.endElement(uri, localName, qName);
if (qName.equals("classB")) {
isClassB = false;
return;
} else if (isClassB && qName.equals("classmate")) {
classb_list.add(classb);
classb = null;
}
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
上面处理器写好了,下面简单几句代码可以完事了。
1.先new一个SAXPaser的对象,使用工厂方法SAXParserFactory.newInstance().newSAXParser()。
2.再new一个刚才写好的处理器的对象,new SaxHandler()。
3.第1步的SAXPaser的对象里有paser个方法,paser(参数1,参数2),参数1是数据,参数2是解析的处理器。一句代码,处理器就立马全速运行了。
4.SAXPaser的paser方法没有返回值,注意上面处理器的代码,我写了一个getData方法返回数据的,执行完paser方法,就可以调用处理器的getData方法,获得B班所有同学的信息,这数据类型是List集合,不用说遍历就可以获得每个同学的信息了。
SAXRunnable.java的片段
try {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
SaxHandler saxHandler = new SaxHandler();
saxParser.parse(is, saxHandler);
StringBuffer sb = new StringBuffer();
for (ClassmateBeen c : saxHandler.getData()) {
// Log.d("sax",c.getId()+","+c.getName());
sb.append(c.toString());
sb.append("\r\n");
}
sb.append("paser for SAXParser\r\n");
callback.CallbackData(sb);
// Log.d("sax","end");
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
(2)XmlPullPaser
这个解析方式,原理和SAXPaser大同小异的,可以随时停止的,不会想SAXPaser那样全速运行的,也没有什么专门处理解析处理器,单凭判断和循环即可。
步骤:
1.new一个XmlPullParser对象,工厂方法XmlPullParserFactory.newInstance().newPullParser()。
2.输入数据,XmlPullParser的setInput(参数1,参数2)方法,参数1是数据的流,参数2是数据编码方式(都是utf-8的了)。
3.跟上面说的解析处理器相似,XmlPullParser有几个状态,START_DOCUMENT,END_DOCUMENT,START_TAG,TEXT,END_TAG,应该知道这五个是什么了吧。
4.判断和循环方式操作的步骤:(其实跟上面的重写的解析处理器的逻辑一样的,书写代码的形式不一样而已)
(1)先从XmlPullParser获取首个状态,再写个while循环 while (eventCode != org.xmlpull.v1.XmlPullParser.END_DOCUMENT),意思是指,到了根标签的末尾,才结束循环,意思就是解析完成。注意的是,与SAXPaser不同的是,采用while循环 代替 解析处理器,eventCode第一次循环外获取,之后都在循环结尾获取XmlPullParser的下一步状态,直到END_DOCUMENT状态才结束循环。
(2)while循环里,switch条件语句,分别列出了START_DOCUMENT,START_TAG,END_TAG,这三种状态的操作,END_DOCUMENT这个状态确实没什么用,上面也没写,TEXT获取文本的状态为何没写呢,这里我有个更方便的写法可以省略它。
(3)START_DOCUMENT状态,一如既往地实例化一个储存B班所有同学的信息的List集合。
意思是让XmlPullParser执行下一步,返回的肯定是TEXT状态,paser.getText()方法可以获得标签的内容,也就是姓名。这样做就不用等到结尾的时候,才执行paser.next(),少跑了一次循环。
(5)循环也做了跟SAXPaser的优化,用return加速执行速度。最后,照旧了,跑完循环,遍历储存Classmate的List集合,获得每个同学的信息。
XmlPullParserRunnable.java
private StringBuffer paserXML(InputStream is) {
StringBuffer sb = new StringBuffer();
List<ClassmateBeen> classB_Member = null;
ClassmateBeen classmate = null;
org.xmlpull.v1.XmlPullParser paser = null;
try {
paser = XmlPullParserFactory.newInstance().newPullParser();
paser.setInput(is, "utf-8");
int eventCode = paser.getEventType();
boolean isClassB = false;
while (eventCode != org.xmlpull.v1.XmlPullParser.END_DOCUMENT) {
switch (eventCode) {
case org.xmlpull.v1.XmlPullParser.START_DOCUMENT:
classB_Member = new ArrayList<>();
break;
case org.xmlpull.v1.XmlPullParser.START_TAG:
if (paser.getName().equals("classB")) {
isClassB = true;
} else if (isClassB && paser.getName().equals("classmate")) {
classmate = new ClassmateBeen();
classmate.setId(paser.getAttributeValue(0));
//现在位置到了<classmate>标签,跳到下一部分,也就是两个标签之间的内容
paser.next();
classmate.setClassmate(paser.getText());
} else {
break;
}
break;
case org.xmlpull.v1.XmlPullParser.END_TAG:
if (!isClassB) break;
if (paser.getName().equals("classB")) {
isClassB = false;
} else if (isClassB && paser.getName().equals("classmate")) {
classB_Member.add(classmate);
classmate = null;
}
break;
}
eventCode = paser.next();
}
for (ClassmateBeen c : classB_Member)
sb.append(c.toString()+ "\r\n");
sb.append("paser for XmlPullParser\r\n");
return sb;
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
附加:XmlPullParser的中断功能,这个还没讲的,现在就基于这个demo,假如我只需要获取B班随便两个同学的信息就够了(已知B班有三个同学的信息),这是可以用上这个中断的功能,一旦获取的信息够了两个,立马中断解析,也就是中断循环。有兴趣的同学可以下载demo,然后参考下面写的模型做一下修改。把while循环内最后的一句eventCode = paser.next();改为下面的样子,中断。
中断模型:
while (eventCode != org.xmlpull.v1.XmlPullParser.END_DOCUMENT) {
.........................省略一串代码跟上面片段一致。
if(classB_Member!=null && classB_Member.size()==2 )
eventCode = org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
else
eventCode = paser.next();
}
demo简单的效果图:
上述代码较多,用到的片段我都放上去了,如有疑惑的话,下载demo跑一下。