在Java/Android开发中,我们经常需要从服务器请求信息,返回的数据格式一般都是XML(extensible markup language)或者JSON(JavaScript Object Notation)格式。在Android开发中,轻量级的数据交互首选JSON,但XML依然大量使用在数据量比较大或者特殊字符比较多等数据交换情形,而且XML在可读性方面还是优于JSON格式的。这里简要地记述一下对xml数据解析的三种使用方法,方便日后温习。
XML文档解析可以采用的方法:DOM(Document Object Module)、SAX(Simple API for XML)和PULL方式。DOM和SAX解析方式都已经集成在Java里面了,Sun公司提供了Java API for XML Parsing(JAXP)接口来使用DOM和SAX,我们可以使用任何与JAXP兼容的XML解析器。JAXP接口包含了三个包:
(1)org.w3c.dom W3C推荐的用于XML标准规划文档对象模型的接口。
(2)org.xml.sax 用于对XML进行语法分析的事件驱动的XML简单API(SAX)
(3)javax.xml.parsers解析器工厂工具,可以获得并配置特殊的特殊语法分析器。
而Pull解析方式则需要引入第三方工具包(目前我找到的最新版kxml2-2.3.0.jar,好像还没api文档可供下载http://kxml.objectweb.org)。
DOM方式把一切都当作一个节点,文档节点、元素节点、文本节点、注释节点etc。它把整个XML文档当作一个Document对象,解析时需要把整个xml文档加载到内存中,解析完成后根据XML文档的节点结构生成文件树。可在程序中随意存取文件树,没有次数限制。显然DOM方式并不适合解析大的XML文档,太耗内存。
sax方式具有解析器和事件处理器,解析器负责读取XML文档和向事件处理器发送事件(充当事件源),事件处理器负责对发送的事件响应和进行XML文档处理。SAX方式采用流处理方式,边解析边触发相应的事件。不需要把整个xml文档加载进内存,边解析边丢弃,解析速度快,占用内存少,很适合移动开发。SAX是层次型的解析,只能依次对xml文档的数据流处理一遍,不支持对数据的任意存取操作(自己用变量保存解析结果另说)。使用sax方式不需要事先知道xml文档的每一个节点名称,主要的工作是写事件处理类。
pull方式跟sax方式很像,也是事件驱动型的。pull方式的结构非常简单,最重要的两个方法就是next()和nextToken(),最常用的几个属性【parser是XmlPullParser解析器对象】:
parser.START DOCUMENT
parser.START_TAG
parser.TEXT
parser.END_TAG
parser.END_DOCUMENT
下面贴一下主要的示例代码片段:
首先在tomcat服务器端放一个xml文档person.xml,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
xml version="1.0" encoding="utf-8"?>
<persons>
<person id="23">
<name>叫兽name>
<age>21age>
person>
<person id="20">
<name>李四name>
<age>25age>
person>
<person id="10">
<name>淫贼name>
<age>20age>
person>
persons>
|
在客户端程序里面需要写一个类来操作得到的xml节点信息,这里统一都用Person.java类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class Person {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [age=" + age + ", id=" + id + ", name=" + name + "]";
}
}
|
统一使用HttpUtils.java类从tomcat服务器上面得到xml文档的数据流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public class HttpUtils {
/**
* 根据路径获取服务器端的xml文件数据流
* @param path xml所在的服务器文件路径
* @return InputStream xml文件的数据流
*/
public static InputStream getXML(String path) {
InputStream inputStream = null;
try {
URL url = new URL(path);
if (url != null) {
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(3000);
connection.setDoInput(true);
connection.setRequestMethod("GET");
int code = connection.getResponseCode();
if (code == 200) {//连接成功
inputStream = connection.getInputStream();
return inputStream;
}
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
}
|
DOM方式解析xml文档的主要操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public List<Person> getPersons(InputStream inputStream) throws Exception{
List<Person> list=new ArrayList<Person>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();// 创建一个document解析的工厂
DocumentBuilder builder = factory.newDocumentBuilder();//dom解析器,此时整个xml文件已经保存在内存中
Document document = builder.parse(inputStream);//解析xml文件流获得文档对象
Element element = document.getDocumentElement();// 获得文档元素节点
// element.getFirstChild()//逐个节点往下读
NodeList personNodeList = element.getElementsByTagName("person");
int len=personNodeList.getLength();
for (int i = 0; i < len; i++) {
Element personElement = (Element) personNodeList.item(i);
Person person = new Person();
person.setId(Integer.parseInt(personElement.getAttribute("id")));
NodeList childNodes = personElement.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
if (childNodes.item(j).getNodeType() == Node.ELEMENT_NODE) {//判断节点类型为元素节点
if ("name".equals(childNodes.item(j).getNodeName())) {//name子节点
person.setName(childNodes.item(j).getFirstChild()
.getNodeValue());
} else if ("age".equals(childNodes.item(j).getNodeName())) {//age子节点
person.setAge(Integer.parseInt(childNodes.item(j)
.getFirstChild().getNodeValue()));
}
}
}
list.add(person);
}
return list;
}
|
DOM方式的测试类Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String path="http://localhost:8080/myhttp/person.xml";
InputStream inputStream=HttpUtils.getXML(path);
DomParseService service=new DomParseService();
try {
List<Person> list=service.getPersons(inputStream);
for(Person person:list){
System.out.println(person.toString());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
SAX方式主要操作集中在事件处理器上(代码有点多,省略),简要说说这个事件处理类MyHandler.java的实现。这个类需要继承DefaultHandler类,同时在类的构造函数中传入当前解析的节点名称。主要是重写以下几个方法来处理事件:
1.public void startDocument() throws SAXException {//接收文档开始时触发}
2.public void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {//接收第一个元素时触发事件
3.public void characters(char[] ch, int start, int length)throws SAXException {//接收元素中字符数据时出发,这里面处理xml文档信息}
4.public void endElement(String uri, String localName, String qName)throws SAXException {//遇到文档结束标记时触发}
SAX方式解析的主要业务逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static List<HashMap<String, String>> readXML(
InputStream inputStream, String nodeName) {
try {
SAXParserFactory spFactory=SAXParserFactory.newInstance();//实例化SAX解析器工厂对象
SAXParser parser=spFactory.newSAXParser();//创建解析器
MyHandler handler=new MyHandler(nodeName);//实例化事件处理器
parser.parse(inputStream, handler);//绑定xml流和事件处理器
inputStream.close();
return handler.getList();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
|
PUll方式解析的主要类PullXMLTools.java(测试代码参考DOM方式):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class PullXMLTools {
/**
* @param inputStream 服务器取得的流
* @param encode 编码格式
* @return
* @throws Exception
*/
public static List<Person> parseXML(InputStream inputStream,String encode) throws Exception{
List<Person> list=null;
Person person=null;
//创建一个解析器工厂
XmlPullParserFactory factory=XmlPullParserFactory.newInstance();
//获得xml解析类的引用
XmlPullParser parser =factory.newPullParser();
parser.setInput(inputStream, encode);
//获得事件的类型
int eventType=parser.getEventType();
while(eventType!=XmlPullParser.END_DOCUMENT){
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
list=new ArrayList<Person>();//新建一个list存储对象
break;
case XmlPullParser.START_TAG:
if("person".equals(parser.getName())){
person=new Person();
person.setId(Integer.parseInt(parser.getAttributeValue(0)));//取出属性值
}else if("name".equals(parser.getName())){
person.setName(parser.nextText());
}else if("age".equals(parser.getName())){
person.setAge(Integer.parseInt(parser.nextText()));//
}
break;
case XmlPullParser.END_TAG:
if("person".equals(parser.getName())){
list.add(person);
person=null;
}
break;
}
eventType=parser.next();
}
return list;
}
}
|
============================================================================================
============================================================================================
跟xml解析一样,json也有很多可供选择的解析包,其中比较常用的有jackson、gson、org.json等(PS:据说阿里的fastjson也不错,可惜那个文档真心蛋疼)。Android一开始就自带了org.json的解析包,在Android 3.0开始又集成了google自己的gson解析包,即新增的android.util.JsonReader和android.util.JsonWriter类。由于目前Android 2.3等低版本仍然占有比较大的比重,从兼容性的角度考虑,目前开发中一般还是选择org.json或者导入gson等解析包
json的基本格式
从json的全称JavaScript Object Notation就可以猜测它跟JavaScript的“亲戚”关系,其实这个轻量级的数据交换格式是基于JavaScript的一个子集,说白了就是js的对象和数组。json采用了独立于语言的文本格式,有两种基本数据结构:对象和数组(两者各种嵌套形成较复杂的json数据)。
Json Array放在中括号[]里面,如[a,c,d...],就跟我们熟悉的数组没本质区别。数组中的元素可以是string, number, false, true, null, Object对象甚至是array数组。下面是官网给的图解:
Json Object放在大括号{}里面,表示成键值对{key1:value1, key2:value2, key3:value3,....}。其中(在面向对象的语言里)key为对象的属性,value为对应的属性值。key只能是string类型的, 而value可以是string, number, false, true, null, Object对象甚至是array数组, 也就是说可以存在嵌套的情况。下面是官网给的图解:
解析json数据首先需要知道解析的是json数组还是json对象!下面将简单介绍一下gson和org.json包的基本使用。解析json内容前建议先根据json的内容建立相应的存储或者表示结构,本文例子里面都将使用到一个Person实体类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" name ", age=" age "]";
}
//注:这里省略相应的setter和getter方法
}
|
在本文中还用到 一些YY出来的json数据内容:
|
private String jsonArray = "[{"name":"Jack","age":20}, {"name":"mike","age":23},"
" {"name":"mary","age":22}]";//待解析的json数组,数组元素是嵌套的json对象
private String jsonObject="{"name":"Object","age":30}";//待解析的json对象
private List<Person> persons=new ArrayList<Person>();//假定ArrayList里面的数据需要转换成json格式以供传输
//同时在onCreate()里面把persons初始化为:
persons.add(new Person("你妹",11 ));
persons.add(new Person("你弟",23 ));
persons.add(new Person("二货",33 ));
|
使用gson解析包
要想使用gson解析包必须首先下载并导入解析包,目前我在官网上看到的最新版本是gson-2.2.4.jar。gson里面通常可以采用两种方式来解析一个json格式数据:第一种方式就是采用JsonReader 逐字符解析json,利用beginArray()和endArray()方法来标志整个数组的开始和结束,解析json对象时采用类似的beginObject()和endObject()方法来标记开头和结尾。下面的第一种解析json数组的方法基本是**跟Android 3.0以后自带的android.util.JsonReader里面的解析方式类似**:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
/**
* 解析json数组的第一种方式
* @param jsonArray
* @return
*/
public List<Person> parseJson(String jsonArray) {
// TODO Auto-generated method stub
try {
//首先需要一个JsonReader对象,传入一个Reader参数
JsonReader reader=new JsonReader(new StringReader(jsonArray));
reader.beginArray();//根据jsonArray可知道第一步要解析数组 即遇到了数组的"["
while(reader.hasNext()){
arrList.add(readObject(reader));//读取数组元素
}
reader.endArray();//解析数组结束 遇到了数组的"]"
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return arrList;
}
/**
* 解析数组里面的json对象组成的元素
* @param reader
* @return
* @throws Exception
*/
private Person readObject(JsonReader reader) throws Exception {
// TODO Auto-generated method stub
String name=null;
int age=0;
reader.beginObject();//开始解析对象,遇到了数组元素的'{'
while(reader.hasNext()){//开始解析对象里面的键值对
String key=reader.nextName();//得到key
if(key.equals("name")){
name=reader.nextString();//取得value值
}else if(key.equals("age")){
age=reader.nextInt();
}
}
reader.endObject();//结束对象的解析 遇到了'}'
return new Person(name, age);
}
|
此外,在gson里面还有另外一种集成好的更方便的解析方式,下面这段代码可以达到跟方法一样的解析结果,代码显得更简洁
|
/**
* 解析json数组的第二种方式
* @param jsonArray
* @return
*/
public List<Person> parseJson2(String jsonArray){
TypeToken<List<Person>> list = new TypeToken<List<Person>>() {};
Gson gson=new Gson();
arrList=gson.fromJson(jsonArray, list.getType());
return arrList;
}
|
如果待解析的json数据是一个简单的json对象,那么可以采用类似方法1中的readObject()的步骤去解析,也可以采用类似方法2中的方式:
|
/**
* 解析一个简单的json对象,效果跟法1中的readObject()方法类似
* @param jsonObject
* @return
*/
public List<Person> parseJsonObject(String jsonObject){
Gson gson=new Gson();
arrList.add(gson.fromJson(jsonObject, Person.class));
return arrList;
}
|
两种方式解析String jsonArray的结果(输出格式按Person类重写的toString()方法):
String jsonObject的解析结果(输出格式按Person类重写的toString()方法):
上面这些两种方法都是关于解析json数据的,比如说服务器给客户端传来的json格式数据就可以采用相似的方式去解析。下面简单介绍一下如何把上面YY出来的List persons里面的内容转变成json数组,以便客户端上传给服务器:
|
public String toJsonString(List<Person> persons) {
Gson gson=new Gson();
return gson.toJson(persons);
}
|
运行结果为:
==========================================================================================================================
使用org.json来解析
Android 里面的org.json包里面有四个类JSONArray、JSONObject、JSONStringer、JSONTokener和一个异常JSONException。这几个类的主要作用为:
- JSONArray:表示的就是上文提到的json数组
- JSONObject:表示的就是上文提到的json对象
- JSONStringer:这个类主要用来生成符合json格式的文本,可以减少由于格式错误导致的程序异常。
- JSONTokener:负责把一个json格式文本转换成相应的对象,这个类既可以把json文本转换成对象(如JSONObject,JSONArray,String,Boolean,Integer,Long,Double or NULL),还可以读取其中的一个个字符(char)。正如官方文档上说的,大多数用户只是使用到这个类的构造方法和nextValue()方法。
JSONArray和JSONObject类
提供的方法主要有:各种构造方法、get××()、opt××(),put(××××)。其中put()方法用来添加或者替换数值,get××和opt××方法在功能上非常类似,都可以根据key或者索引取得json里面的数据项,但它们有个非常重要的区别:
- get××()方法没找到相应key的情况下,会抛出JSONException异常
- opt××()方法没找到相应key的情况下,会返回一个null值,并不会抛出JSONException异常
下面通过一个简单的例子来看看JSONObject类的使用(JSONArray类似,略):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//JSONObject JSONArray的使用示例
String json="{'name':'阿波','age':30}";
Map<String,Object> map=new HashMap<String, Object>();
map.put("name", "Map");
map.put("age", 11);
try {
// JSONObject object=new JSONObject(json);//利用String来构造
JSONObject object=new JSONObject(map);//利用map来构造,还有其他构造方法不再罗列了
//两个方法的主要区别上面已经简单介绍了
tv_json.setText("取得的名字:"+ object.getString("name") +"n取得的年龄:" +object.optInt("age"));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
|
解析的结果为:
JSONStringer类
主要的方法有:array()、endArray()、object()、endObject()、key()、value()和toString()。显然这几个方法都是成对使用的,具体点就是:
- object()表明开始一个JSON对象,即添加"{",endObject()表结束,即添加"}";
- array()表明开始一个JSON数组,即添加一个"[" ,endArray()表结束,即添加"]" ;
- key()表示添加一个key,value()表示添加一个value,构成key:value键值对。
简单的使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
try {
//l利用JSONStringer生成一串json数据,缩进略怪,易懂就行
JSONStringer stringer=new JSONStringer().object()
.key("name").value("阿海")
.key("age").value("20")
.key("address").value("北京海淀")
.key("girlfriend")
.array()
.value("阿猫").value("阿狗").value("阿猪")
.endArray()
.key("phone").value("13411111111")
.endObject();
tv_json.setText("把stringer转成字符串输出:"+stringer.toString());//输出显示
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
|
得到的结果(如下图)为一个json对象里面嵌套一个简单的json数组,更复杂的json格式文本按这方式建立即可:
JSONTokener类
Android集成的org.json包里面的JSONTokener只有一个构造方法JSONTokener(String in) ,类里面提供了不少方法,但一般常使用的只有nextValue()方法,其他主要方法的详细解释见代码注释。
简单的使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//JSONTokener的使用示例
String json="{'name':'阿波','age':30,'兴趣':['读书','睡觉','吃饭']}";
try {
JSONTokener tokener=new JSONTokener(json);//org.json包里面的JSONTokener只有唯一构造函数
// JSONObject object=new JSONObject(tokener);//这个也是JSONObject的构造方法之一,将一次性把这个JSONObject读取完
// tv_json.setText(object.toString());
String a,b,c,d;
a=tokener.nextString('g');//当前位置到第一个 g 中间的内容(不包括当前字符 g ),即 {'name':'阿波','a
b=String.valueOf(tokener.next());//从当前所在位置(age的'g'处)继续往下读一个字符(若遇到中文也是一个字符) ,即 e
c=String.valueOf(tokener.next(10));//age后面数10个字符,即 ':30,'兴趣':
//读取下一个JSONObject,JSONArray,String,Boolean,Integer,Long,Double or NULL
JSONArray array=(JSONArray) tokener.nextValue();//读取下一个JSONArray,即 ['读书','睡觉','吃饭']
d=String.valueOf(tokener.nextClean());//读取下一个非空白或者评论内容的字符,这里即 }
tv_json.setText("tokener.nextString('g')=" a
+"ntokener.next()=" b
+"ntokener.next(10)=" c
+"ntokener.nextValue())=" array.toString()
+"ntokener.nextClean()=" d);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
|
运行结果:
以上示例代码的tv_json.setText()是上文提到的TextView对象,但tv_json.setText()输出里面的字符串连接符(加号+),有些浏览器没显示出来,忽略它。