XML和JSON解析
在网络上传输数据时最常用的格式有两种:XML和JSON。本文主要就是学习如何对这两种常用的数据格式进行解析。
1、XML和JSON的定义
- XML:扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML使用DTD(document type definition)文档类型定义来组织数据;格式统一,跨平台和语言,早已成为业界公认的标准。XML是标准通用标记语言 (SGML) 的子集,非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。
1 <apps> 2 <app> 3 <id>1</id> 4 <name>matlab</name> 5 <version>12.5</version> 6 </app> 7 <app> 8 <id>2</id> 9 <name>chorme</name> 10 <version>9.986</version> 11 </app> 12 <app> 13 <id>3</id> 14 <name>Google Maps</name> 15 <version>10.4</version> 16 </app> 17 </apps>
- JSON:JavaScript Object Notation,一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。可在不同平台之间进行数据交换。JSON采用兼容性很高的、完全独立于语言文本格式,同时也具备类似于C语言的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)体系的行为。这些特性使JSON成为理想的数据交换语言。JSON基于JavaScript Programming Language , Standard ECMA-262 3rd Edition - December 1999 的一个子集。
JSON就是一串字符串,只不过元素会使用特定的符号标注。
{} 双括号表示对象
[] 中括号表示数组
"" 双引号内是属性或值
: 冒号表示后者是前者的值(这个值可以是字符串、数字、也可以是另一个数组或对象)
1 [{"id":"1","name":"matlab","version","12.5"} 2 {"id":"2","name":"chorme","version","9.986"} 3 {"id":"3","name":"Google Maps","version","10.4"}]
2、XML和JSON的优缺点
- XML的优缺点
- XML的优点
- 格式统一,符合标准;
- 容易与其他系统进行远程交互,数据共享比较方便。
-
- XML的缺点
- XML文件庞大,文件格式复杂,传输占带宽;
- 服务器端和客户端解析XML花费较多的资源和时间。
- 服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护;
- 客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码;
- 两者对比:
- 相同点:
- 两者的数据可读性基本相同
- 两者拥有同样丰富的解析手段
- 异同点:
- json的数据体积更小
- json与JS的交互更加方便
- json的解析速度更快
- xml对数据的描述性更好
3、XML和JSON的解析
我们先整体上列一个思路,对于这两种数据格式的解析,每一种数据都有多种解析方法,本文对每一种数据都提供两种经常用到的两种方式:
- XML格式解析:Pull解析方式、SAX解析方式、DOM解析方式
- Pull解析方式:Pull解析器的运行方式与 SAX 解析器相似。它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件。事件将作为数值代码被发送,因此可以使用一个switch对感兴趣的事件进行处理。当元素开始解析时,调用parser.nextText()方法可以获取下一个Text类型元素的值。
1 private void parseXMLWithPull(String xmlData){ 2 try { 3 //创建一个xml解析的工厂 4 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 5 //获得xml解析类的引用 6 XmlPullParser xmlPullParser = factory.newPullParser(); 7 //以流的方式传入需要解析的xml数据 8 xmlPullParser.setInput(new StringReader(xmlData)); 9 //获得事件的类型 10 int eventType = xmlPullParser.getEventType(); 11 String id = "" ; 12 String name = "" ; 13 String version = "" ; 14 //判断是否到了文档结束位置 15 while(eventType != XmlPullParser.END_DOCUMENT){ 16 String nodeName = xmlPullParser.getName() ; 17 switch(eventType){ 18 19 //遇到标签元素 20 case XmlPullParser.START_TAG: 21 if("id".equals(nodeName)){ 22 //取出属性值,0是代表第0个属性 23 id = xmlPullParser.nextText(); 24 } else if("name".equals(nodeName)){ 25 //获取该节点的内容 26 name = xmlPullParser.nextText(); 27 }else if("version".equals(nodeName)){ 28 //获取该节点的内容 29 version = xmlPullParser.nextText(); 30 } 31 break; 32 //标签结束 33 case XmlPullParser.END_TAG: 34 if("app".equals(xmlPullParser.getName())){ 35 //这里可以做一些初始化,或者log记录 36 Log.d("MainAvtivity", "id is" + id) ; 37 Log.d("MainAvtivity", "name is" + name) ; 38 Log.d("MainAvtivity", "version is" + version) ; 39 } 40 break; 41 default: 42 break ; 43 } 44 //循环 45 eventType = xmlPullParser.next(); 46 } 47 } catch (Exception e) { 48 // TODO Auto-generated catch block 49 e.printStackTrace(); 50 } 51 }
- SAX解析方式:Simple API for XML,SAX是一个解析速度快并且占用内存少的xml解析器,非常适合用于 Android等移动设备。 SAX解析XML文件采用的是事件驱动,也就是说,它并不需要解析完整个文档,在按内容顺序解析文档的过程中,SAX会判断当前读到的字符是否合法XML 语法中的某部分,如果符合就会触发事件。所谓事件,其实就是一些回调(callback)方法,这些方法(事件)定义在ContentHandler接口。
1 private void parseXMLWithSAX(String xmlData){ 2 try { 3 //第一步:新建一个工厂类SAXParserFactory,并获取其实例 4 SAXParserFactory factory = SAXParserFactory.newInstance(); 5 //第二步:让工厂类产生一个SAX的解析类SAXParser的对象 6 SAXParser parser = factory.newSAXParser(); 7 //第三步:从SAXPsrser中得到一个XMLReader实例 8 XMLReader xmlReader = parser.getXMLReader(); 9 //第四步:把自己写的handler注册到XMLReader中,一般最重要的就是ContentHandler 10 MySaxHandler handler = new MySaxHandler() ; 11 xmlReader.setContentHandler(handler); 12 //第五步:将一个xml文档或者资源变成一个java可以处理的InputStream流后,解析正式开始 13 xmlReader.parse(new InputSource(new StringReader(xmlData))); 14 } catch (Exception e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } 18 }
最重要、最关键的就是第四步,handler的实现。下面是其实现的代码:
1 /* 2 * 实现一个ContentHandler一般要一下几个步骤: 3 * 4 * 1、声明一个类,继承DefaultHandler。DefaultHandler是一个基类,这个类里面简单实现了 5 * 一个ContentHandler。我们只需要重写里面的方法即可。 6 * 2、重写 startDocument() 和 endDocument(),一般解析将正式解析之前的一些初始化工资放 7 * 到startDocument()里面,收尾的工作放到endDocument()里面。 8 * 3、重写startElement(),XML解析器遇到XML里面的tag时就会调用这个函数。经常在这个函数内是 9 * 通过localName俩进行判断而操作一些数据。 10 * 4、重写characters()方法,这是一个回调方法。解析器执行完startElement()后,解析完节点的内 11 * 容后就会执行这个方法,并且参数ch[]就是节点的内容。这个例子里我们根据currentstate的不同,来 12 * 判断当前那个tag的内容,并放到合适的实体类中。 13 * 5、重写endElement()方法,这个方法与startElement()相对应,解析完一个tag节点后,执行这个方法。 14 * 再找个例子中,如果解析一个item结束,就将RSSIiem添加到RSSFeed中。 15 * 16 */ 17 class MySaxHandler extends DefaultHandler{ 18 19 private String nodeName ; 20 private StringBuilder id ; 21 private StringBuilder name ; 22 private StringBuilder version ; 23 24 /** 25 * 当SAX解析器解析到XML文档开始时,会调用的方法 26 */ 27 @Override 28 public void startDocument() throws SAXException { 29 id = new StringBuilder() ; 30 name = new StringBuilder() ; 31 version = new StringBuilder() ; 32 } 33 34 /** 35 * 当SAX解析器解析到XML文档结束时,会调用的方法 36 */ 37 @Override 38 public void endDocument() throws SAXException { 39 super.endDocument(); 40 } 41 42 /** 43 * 当SAX解析器解析到某个属性值时,会调用的方法 44 * 其中参数ch记录了这个属性值的内容 45 */ 46 @Override 47 public void characters(char[] ch, int start, int length)throws SAXException { 48 //根据当前的节点名判断将内容添加到哪一个StringBuilder上 49 if("id".equals(nodeName)){ 50 id.append(ch, start, length) ; 51 }else if("name".equals(nodeName)){ 52 name.append(ch, start, length) ; 53 }else if("version".equals(nodeName)){ 54 version.append(ch, start, length) ; 55 } 56 } 57 58 /** 59 * 当SAX解析器解析到某个元素开始时,会调用的方法 60 * 其中localName记录的是元素属性名 61 */ 62 @Override 63 public void startElement(String uri, String localName, String qName, 64 Attributes attributes) throws SAXException { 65 nodeName = localName ; 66 } 67 68 /** 69 * 当SAX解析器解析到某个元素结束时,会调用的方法 70 * 其中localName记录的是元素属性名 71 */ 72 @Override 73 public void endElement(String uri, String localName, String qName) 74 throws SAXException { 75 if("app".equals(localName)){ 76 //这里可以做一些初始化,或者log记录 77 Log.d("MainAvtivity", "id is" + id) ; 78 Log.d("MainAvtivity", "name is" + name) ; 79 Log.d("MainAvtivity", "version is" + version) ; 80 } 81 } 82 }
- DOM解析方式: DOM解析XML文件时,会将XML文件的所有内容读取到内存中,然后允许您使用DOM API遍历XML树、检索所需的数据。使用DOM操作XML的代码看起来比较直观,并且,在某些方面比基于SAX的实现更加简单。但是,因为DOM需要将 XML文件的所有内容读取到内存中,所以内存的消耗比较大,特别对于运行Android的移动设备来说,因为设备的资源比较宝贵,所以建议还是采用SAX 来解析XML文件,当然,如果XML文件的内容比较小采用DOM是可行的。(不适合Android移动设备)
- Pull解析方式:Pull解析器的运行方式与 SAX 解析器相似。它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件。事件将作为数值代码被发送,因此可以使用一个switch对感兴趣的事件进行处理。当元素开始解析时,调用parser.nextText()方法可以获取下一个Text类型元素的值。
- JSON格式解析:使用JsonObject解析和使用GSON解析。可以参考:Android学习笔记45:JSON数据解析(GSON方式)
- 使用JsonObject解析:可以看作是一个json对象,这是系统中有关JSON定义的基本单元,其包含一对(Key/Value)数值。它对外部(External:应用toString()方法输出的数值)调用的响应体现为一个标准的字符串(例如:{"JSON": "Hello, World"},最外被大括号包裹,其中的Key和Value被冒号":"分隔)。其对于内部(Internal)行为的操作格式略微,例如:初始化一个JSONObject实例,引用内部的put()方法添加数值:new JSONObject().put("JSON", "Hello, World!"),在Key和Value之间是以逗号","分隔。Value的类型包括:Boolean、JSONArray、JSONObject、Number、String或者默认值JSONObject.NULL object。
1 private void parseJSONWithJSONObject(String jsonData){ 2 try { 3 JSONArray jsonArray = new JSONArray(jsonData); 4 for (int i = 0; i < jsonArray.length(); i++) { 5 JSONObject jsonObject = jsonArray.getJSONObject(i); 6 String id = jsonObject.getString("id"); 7 String name = jsonObject.getString("name"); 8 String version = jsonObject.getString("version"); 9 10 // 解析完之后对这些数据进行处理,这里我们只是Log输出 11 Log.d("MainAvtivity", "id is" + id); 12 Log.d("MainAvtivity", "name is" + name); 13 Log.d("MainAvtivity", "version is" + version); 14 } 15 } catch (Exception e) { 16 e.printStackTrace(); 17 } 18 }
- 使用GSON解析:要创建和解析JSON数据,也可以使用GSON来完成。GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。使用GSON,可以很容易的将一串JSON数据转换为一个Java对象,或是将一个Java对象转换为相应的JSON数据。之一,GSON是谷歌的开源库,并没有被添加到Android官方的API中,因此要使用这个功能,我们需要在项目中添加一个GSON的jar包。
在GSON的API中,提供了两个重要的方法:String toJson()和<T> fromJson()方法。其中,toJson()方法用来实现将Java对象转换为相应的JSON数据,以字符串形式返回,fromJson()方法则用来实现将JSON数据转换为相应的Java对象。所以,我们在解析JSON数据时,可以直接通过使用前面提到的fromJson()方法将JSON数据(实际上是字符串类型)转化为我们所想要的一种类型,因此,我们一般需要自定义一个相关的类来将我们需要的数据进行封装,方面我们通过fromJson()方法返回获得。这里我们将一个对象的数据封装成App类。1 private void parseJSONWithGson(String jsonData){ 2 Gson gson= new Gson() ; 3 //我们借助TypeTolen将期望解析成的数据类型传入到fromJson()方法中 4 List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType()) ; 5 for(App app:appList){ 6 // 解析完之后对这些数据进行处理,这里我们只是Log输出 7 Log.d("MainAvtivity", "id is" + app.getId()); 8 Log.d("MainAvtivity", "name is" +app.getName()); 9 Log.d("MainAvtivity", "version is" + app.getVersion()); 10 } 11 }
1 /* 2 * 我们将一个JSON对象({}之间表示一个对象)封装成App类 3 */ 4 class App{ 5 String id ; 6 String name ; 7 String version ; 8 public String getId() { 9 return id; 10 } 11 public void setId(String id) { 12 this.id = id; 13 } 14 public String getName() { 15 return name; 16 } 17 public void setName(String name) { 18 this.name = name; 19 } 20 public String getVersion() { 21 return version; 22 } 23 public void setVersion(String version) { 24 this.version = version; 25 } 26 }
-
FastJSON:不详细解释。
- 使用JsonObject解析:可以看作是一个json对象,这是系统中有关JSON定义的基本单元,其包含一对(Key/Value)数值。它对外部(External:应用toString()方法输出的数值)调用的响应体现为一个标准的字符串(例如:{"JSON": "Hello, World"},最外被大括号包裹,其中的Key和Value被冒号":"分隔)。其对于内部(Internal)行为的操作格式略微,例如:初始化一个JSONObject实例,引用内部的put()方法添加数值:new JSONObject().put("JSON", "Hello, World!"),在Key和Value之间是以逗号","分隔。Value的类型包括:Boolean、JSONArray、JSONObject、Number、String或者默认值JSONObject.NULL object。