• 3.4 网络存储数据


    调用WebService返回的数据或是解析HTTP协议(见5.1节)实现网络数据交互。

    存储格式一般为XML或是JSON。本节主要讲解这两种类型的数据解析。

    XML被设计用来结构化、存储以及传输信息。

    JSON:JavaScript对象表示法(JavaScript Object Notation), 是一种轻量级的数据交换格式, 易于人阅读和编写, 同时也易于机器解析和生成。

    3.4.1 解析XML

      常见的XML解析器分别为DOM解析器、SAX解析器和PULL解析器。另,对于频繁用XML交互的话可以使用xStream框架,一个简单的工具包,用来把对象序列化成xml配置文件,并且也可以把xml反序化成对象。

    第一种方式: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文件。

    第二种方式:SAX解析器:

      Simple API for XML解析器是基于事件的解析器,事件驱动的流式解析方式是,从文件的开始顺序解析到文档的结束,不可暂停或倒退。它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。 

      SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。

      SAX的工作原理:SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。

      在SAX中,事件源是org.xml.sax包中的XMLReader,它通过parser()方法来解析XML文档,并产生事件。

      事件处理器是org.xml.sax包中ContentHander、DTDHander、ErrorHandler,以及EntityResolver这4个接口。XMLReader通过相应事件处理器注册方法setXXXX()来完成的与ContentHander、DTDHander、ErrorHandler,以及EntityResolver这4个接口的连接。(可知,我们需要XmlReader 以及DefaultHandler来配合解析xml。)

    常用的SAX接口和类:

    • Attrbutes:用于得到属性的个数、名字和值。 
    • ContentHandler:定义与文档本身关联的事件(例如,开始和结束标记)。大多数应用程序都注册这些事件。
    • DTDHandler:定义与DTD关联的事件,但并不完整。要对DTD进行语法分析,请使用可选的DeclHandler。
    • DeclHandler是SAX的扩展。不是所有的语法分析器都支持它。
    • EntityResolver:定义与装入实体关联的事件。只有少数几个应用程序注册这些事件。
    • ErrorHandler:定义错误事件。许多应用程序注册这些事件以便用它们自己的方式报错。
    • DefaultHandler:它提供了这些接口的缺省实现。在大多数情况下,为应用程序扩展DefaultHandler并覆盖相关的方法要比直接实现一个接口更容易。

    第三种方式:PULL解析器:

      Android并未提供对Java StAX API的支持。但是,Android附带了一个pull解析器,其工作方式类似于StAX。它允许用户的应用程序代码从解析器中获取事件,这与SAX解析器自动将事件推入处理程序相反。

      PULL解析器的运行方式和SAX类似,基于事件的模式。不同的是,在PULL解析过程中返回的是数字,且我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。

    • 读取到xml的声明返回             START_DOCUMENT;
    • 读取到xml的结束返回             END_DOCUMENT ;
    • 读取到xml的开始标签返回     START_TAG                  调用XmlPullParser.next()循环读取节点
    • 读取到xml的结束标签返回     END_TA                        switch处理
    • 读取到xml的文本返回             TEXT

      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解析器相关的错误。

    [附加]第四种方式:Android.util.Xml

      在Android API中,另外提供了Android.util.Xml类,同样可以解析XML文件,使用方法类似SAX,也都需编写Handler来处理XML的解析,但是在使用上却比SAX来得简单 ,如下所示:

    MyHandler myHandler=new MyHandler();
    Android.util.Xml.parse(ur1.openConnection().getlnputStream(), Xml.Encoding.UTF-8, myHandler);

    几种解析技术的比较与总结: 

    对于Android的移动设备而言,因为设备的资源比较宝贵,内存是有限的,所以我们需要选择适合的技术来解析XML,这样有利于提高访问的速度。

    1)  DOM在处理XML文件时,将XML文件解析成树状结构并放入内存中进行处理。当XML文件较小时,我们可以选DOM,因为它简单、直观。

    2)  SAX则是以事件作为解析XML文件的模式,它将XML文件转化成一系列的事件,由不同的事件处理器来决定如何处理。XML文件较大时,选择SAX技术是比较合理的。虽然代码量有些大,但是它不需要将所有的XML文件加载到内存中。这样对于有限的Android内存更有效,而且Android提供了一种传统的SAX使用方法以及一个便捷的SAX包装器。 使用Android.Util.Xml类,从示例中可以看出,会比使用 SAX来得简单。

    3)  XML pull解析并未像SAX解析那样监听元素的结束,而是在开始处完成了大部分处理。这有利于提早读取XML文件,可以极大的减少解析时间,这种优化对于连接速度较漫的移动设备而言尤为重要。对于XML文档较大但只需要文档的一部分时,XML Pull解析器则是更为有效的方法。

    Dom 解析     
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.xml.sax.SAXException;
    import com.leo.sax_parser.model.Person;
    public class DomParser {
        public static List<Person> readXMLByDom(InputStream input)
                throws ParserConfigurationException, SAXException, IOException {
            List<Person> persons = new ArrayList<Person>();
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(input);
            Element root = document.getDocumentElement();
            NodeList nodes = root.getElementsByTagName("person");
            for (int i = 0; i < nodes.getLength(); i++) {     //person对象个数
                Element element = (Element) nodes.item(i);
                Person person = new Person();
                person.setId(element.getAttribute("id"));
                NodeList childNodes = element.getChildNodes();
                for (int j = 0; j < childNodes.getLength(); j++) {   // 对象中属性的个数:id,name,age,phoneNumber
                    Node child = childNodes.item(j);
              // 解决getChildNodes().getLength()与实际不符的问题
                    if (child.getNodeType() != Node.ELEMENT_NODE) { 
                        continue;
                    }
                    Element childElement = (Element) child;
                    Log.i("DomParser", childElement.getNodeName() + ":" + childElement.getTextContent().trim());
                    if ("name".equals(childElement.getNodeName())) {
                        person.setName(childElement.getTextContent().trim());
                    } else if ("age".equals(childElement.getNodeName())) {
                        person.setAge(Integer.parseInt(childElement.getTextContent().trim()));
                    } else if ("phoneNumber".equals(childElement.getNodeName())) {
                        person.setPhoneNumber(Integer.parseInt(childElement.getFirstChild().getNodeValue()));
                    }
                }
                persons.add(person);
            }
            return persons;
        }
    }

      

    [代码]SAX 解析   
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException; 
    import org.xml.sax.helpers.DefaultHandler;
    public class SAX_handler extends DefaultHandler {
    private List<Person> persons;
    private String perTag;
    private Person person;
    
        public List<Person> getPersons() {
            return persons;
        }
        @Override
        public void startDocument() throws SAXException {
            persons = new ArrayList<Person>(); //初始化用于存放person对象的persons,用于存放读取到的相应的信息。
        }
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if ("person".equals(localName)) {
                for (int i = 0; i < attributes.getLength(); i++) {
                    Log.i(Tag, "attributeName:" + attributes.getLocalName(i) + "attributeValue:" + attributes.getValue(i));
                    person = new Person();
                    person.setId(attributes.getValue(i));
                }
            }
            perTag = localName;
        }
        @Override
        public void characters(char[] ch, int start, int length)  throws SAXException {
            String data = new String(ch, start, length);
            if (!"".equals(data.trim())) 
                Log.i(Tag, "Content:" + data);
            if ("name".equals(perTag)) {  person.setName(data);  }
         else if ("age".equals(perTag)) {  person.setAge(Integer.parseInt(data));      }  
         else if ("phoneNumber".equals(perTag)) {  person.setPhoneNumber(Integer.parseInt(data));        }
        }
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (person != null&&"person".equals(localName)) {
                persons.add(person);
                person = null;
            }
            perTag = null;
        }
        @Override
        public void endDocument() throws SAXException {  Log.i(Tag, "endDocument");    }
    }


    Pull 解析 
    import org.xmlpull.v1.XmlPullParser;
    import org.xmlpull.v1.XmlPullParserException;
    import android.util.Xml;
    public class PullParser {
        public static List<Person> readXML(InputStream inputstream) throws XmlPullParserException, IOException {
            List<Person> persons = null;
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(inputstream, "UTF-8");
            int eventCode = parser.getEventType();
            Person person = null;
            while (eventCode != XmlPullParser.END_DOCUMENT) {
                switch (eventCode) {
                case XmlPullParser.START_DOCUMENT:
                    persons = new ArrayList<Person>();
                    break;
                case XmlPullParser.START_TAG:
                    if ("person".equals(parser.getName())) {
                        person = new Person();
                        person.setId(parser.getAttributeValue(0));
                    }else if(person != null){
                        if ("name".equals(parser.getName())) {
                            person.setName(parser.nextText());
                        }else if("age".equals(parser.getName())){
                            person.setAge(Integer.parseInt(parser.nextText()));
                        }else if("phoneNumber".equals(parser.getName())){
                            person.setPhoneNumber(Integer.parseInt(parser.nextText()));
                        }
                    }
                    break;
                case XmlPullParser.END_TAG:
                        if ("person".equals(parser.getName()) && person!= null) {
                            persons.add(person);
                            person =null;
                        }
                    break;
                default:
                    break;
                }
                eventCode = parser.next();
            }
            return persons;
        }
    }

    XMLJSON的区别:

    • XML的主要组成成分是element、attribute和element content。
    • JSON的主要组成成分是object、array、string、number、boolean(true/false)和null。
    • JSON相比XML的不同之处:没有结束标签,更短,读写的速度更快,能够使用内建的 JavaScript eval() 方法进行解析,使用数组,不使用保留字。总之: JSON 比 XML 更小、更快,更易解析。
    • XML需要选择怎么处理element content的换行,而JSON string则不须作这个选择。
    • XML只有文字,没有预设的数字格式,而JSON则有明确的number格式,这样在locale上也安全。
    • XML映射数组没大问题,就是数组元素tag比较重复冗余。JSON 比较易读。
    • JSON的true/false/null也能容易统一至一般编程语言的对应语义。

    解析XMLSAX可以快速扫描一个大型的XML文档,当它找到查询标准时就会立即停止,然后再处理之。DOM是把XML全部加载到内存中建立一棵树之后再进行处理。所以DOM不适合处理大型的XML【会产生内存的急剧膨胀】。

    解析JSON:Android自带了JSON解析的相关API(org.json)。谷歌的Gson,阿里的FastJson,还有一个jackJson。有人说jackJson解析速度快,大数据时FastJson要比Gson效率高,小数据时反之。


    3.4.2 解析Json

    A、服务器端将数据转换成json字符串

       首先、服务器端项目要导入Gson的jar包到BuiltPath中。然后将数据转为json字符串,核心函数是:

    public static String createJsonString(Object value){
            Gson gson = new Gson();
            String str = gson.toJson(value);
            return str;
        }

    B、客户端将json字符串转换为相应的javaBean

        首先客户端也要导入gson的两个jar包,json的jar就不需要导入了(因为android已经集成了json的jar包)

    1、客户端获取json字符串

    public class HttpUtil{
        public static String getJsonContent(String urlStr){
            try{// 获取HttpURLConnection连接对象
                URL url = new URL(urlStr);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);  conn.setDoInput(true);   conn.setRequestMethod("GET");
                int respCode = httpConn.getResponseCode();// 获取相应码
                if (respCode == 200)   return ConvertStream2Json(httpConn.getInputStream());
            }
            catch (IOException e){  e.printStackTrace();       }
            return "";
    }
        private static String ConvertStream2Json(InputStream inputStream){
            String jsonStr = ""; ByteArrayOutputStream out = new ByteArrayOutputStream();//相当于内存输出流
            byte[] buffer = new byte[1024];  int len = 0;
            try{// 将输入流转移到内存输出流中
                while ((len = inputStream.read(buffer, 0, buffer.length)) != -1)
                    out.write(buffer, 0, len);
                jsonStr = new String(out.toByteArray());// 将内存流转换为字符串
            }
            catch (IOException e){  e.printStackTrace();  }
            return jsonStr;
        }
    }

    2、使用泛型获取javaBean(核心函数)

    public static <T> T getPerson(String jsonString, Class<T> cls) {
            T t = null;
            try {
                Gson gson = new Gson();
                t = gson.fromJson(jsonString, cls);
            } catch (Exception e) {  }
            return t;
        }
    public static <T> List<T> getPersons(String jsonString, Class<T> cls) {
            List<T> list = new ArrayList<T>();
            try {
                Gson gson = new Gson();
                list = gson.fromJson(jsonString, new TypeToken<List<cls>>() { }.getType());
            } catch (Exception e) {  }
            return list;
        }
    public static List<Map<String, Object>> listKeyMaps(String jsonString) {
            List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
            try {
                Gson gson = new Gson();
                list = gson.fromJson(jsonString, new TypeToken<List<Map<String, Object>>>() { }.getType());
            } catch (Exception e) {   }
            return list;
        }

    3.4.3 Protocol Buffers

    1概述

      ProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,有如XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。目前仅提供了 C++、Java、Python 三种语言的 API。

    2工作原理

      你首先需要在一个 .proto 文件中定义你需要做串行化的数据结构信息。每个ProtocolBuffer信息是一小段逻辑记录,包含一系列的键值对。

      一旦你定义了自己的报文格式(message),你就可以运行ProtocolBuffer编译器,将你的 .proto 文件编译成特定语言的类。这些类提供了简单的方法访问每个字段(像是 query() 和 set_query() ),像是访问类的方法一样将结构串行化或反串行化。

      你可以在不影响向后兼容的情况下随意给数据结构增加字段,旧有的数据会忽略新的字段。所以如果使用ProtocolBuffer作为通信协议,你可以无须担心破坏现有代码的情况下扩展协议。

    3优缺点

      ProtocolBuffer拥有多项比XML更高级的串行化结构数据的特性:

      更简单,小3-10倍,快20-100倍,更少的歧义,可以方便的生成数据存取类

      当然,ProtocolBuffer并不是在任何时候都比XML更合适,例如ProtocolBuffer无法对一个基于标记文本的文档建模,因为你根本没法方便的在文本中插入结构。另外,XML是便于人类阅读和编辑的,而ProtocolBuffer则不是。还有XML是自解释的,而 ProtocolBuffer仅在你拥有报文格式定义的 .proto 文件时才有意义。

    4使用方法

      下载包( http://code.google.com/p/protobuf/downloads/ ),包含了Java、Python、C++的ProtocolBuffer编译器,用于生成你需要的IO类。构建和安装你的编译器,跟随README的指令就可以做到。

      一旦你安装好了,就可以跟着编程指导( http://code.google.com/apis/protocolbuffers/docs/tutorials.html )来选择语言-随后就是使用ProtocolBuffer创建一个简单的应用了。

    1) 所需文件:proto.exe,  protobuf-java-2.4.1.jar

    2) 建立一个工程TestPb,在下面建立一个proto文件,用来存放【.proto】文件

    3) 将proto.exe放在工程目录下

    4) 建立一个msg.proto文件:cmd 打开命令工具,输入命令:protoc  --java_out=./ msg.proto

    5) 再次进入目录后会发现该目录多了一个文件夹,即以该proto的package命名的的目录,会产生一个Msg.java的文件,这时这个文件就可以使用到我们的java或者 android 工程了。

    6) 导如jar包到工程中,就可以使用protobuf了

    7) Protobuf 实现Socket通信


    3.4.4 FlatBuffers

    1概述

      FlatBuffers是一个开源的、跨平台的、高效的、提供了C++/Java接口的序列化工具库。它是Google专门为游戏开发或其他性能敏感的应用程序需求而创建。尤其更适用于移动平台,这些平台上内存大小及带宽相比桌面系统都是受限的,而应用程序比如游戏又有更高的性能要求。它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而不需要任何解析开销。

    2特点

    • 对序列化数据的访问不需要打包和拆包——它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而没有任何解析开销;
    • 内存效率和速度——访问数据时的唯一内存需求就是缓冲区,不需要额外的内存分配。
    • 扩展性、灵活性——它支持的可选字段意味着不仅能获得很好的前向/后向兼容性(对于长生命周期的游戏来说尤其重要,因为不需要每个新版本都更新所有数据);
    • 最小代码依赖——仅仅需要自动生成的少量代码和一个单一的头文件依赖,很容易集成到现有系统中。
    • 强类型设计——尽可能使错误出现在编译期,而不是等到运行期才手动检查和修正;
    • 使用简单——生成的C++代码提供了简单的访问和构造接口;而且如果需要,通过一个可选功能可以用来在运行时高效解析Schema和类JSON格式的文本;
    • 跨平台——支持C++、Java,而不需要任何依赖库;在最新的gcc、clang、vs2010等编译器上工作良好;

    3 FlatBuffersProtocol Buffers以及Json的比较:

      FlatBuffers的功能和Protocol Buffers很像,他们的最大不同点是在使用具体的数据之前,FlatBuffers不需要解析/解包的过程。同时, FlatBuffers的引用比Protocol Buffers方便很多,只需要包含两三个头文件即可

      JSON作为数据交换格式,被广泛用户各种动态语言之间(当然也包括静态语言)。它的优点是可读性好,同时它的最大的缺点那就是解析时的性能问题了。而且因为它的动态类型特点,你的代码可能还需要多写好多类型、数据检查逻辑。

      在做 Android 开发的时候,JSON 是最常用的数据序列化技术。我们知道,JSON 的可读性很强,但是序列化和反序列化性能却是最差的。解析的时候,JSON 解析器首先,需要在内存中初始化一个对应的数据结构,这个事件经常会消耗 100ms ~ 200ms2;解析过程中,要产生大量的临时变量,造成 Java 虚拟机的 GC 和内存抖动,解析 20KB 的数据,大概会消耗 100KB 的临时内存2。FlatBuffers 就解决了这些问题。

    4使用方法

    1) 编写一个用来定义你想序列化的数据的schema文件(又称IDL),数据类型可以是各种大小的int、float,或者是string、array,或者另一对象的引用,甚至是对象集合;

    2) 各个数据属性都是可选的,且可以设置默认值。

    3) 使用FlatBuffer编译器flatc生成C++头文件或者Java类,生成的代码里额外提供了访问、构造序列化数据的辅助类。生成的代码仅仅依赖flatbuffers.h;

    4) 使用FlatBufferBuilder类构造一个二进制buffer。你可以向这个buffer里循环添加各种对象,而且很简单,就是一个单一函数调用;

    5) 保存或者发送该buffer

    6) 当再次读取该buffer时,你可以得到这个buffer根对象的指针,然后就可以简单的就地读取数据内容;

      简单来说,FlatBuffers 的使用方法是,首先按照使用特定的 IDL 定义数据结构 schema,然后使用编译工具 flatc 编译 schema 生成对应的代码,把生成的代码应用到工程中即可。

      首先,我们需要得到 flatc,这个需要从源码编辑得到。从 GitHub 上 Clone 代码,

    $ git clone https://github.com/google/flatbuffers

      在 Mac 上,使用 Xcode 直接打开 build/Xcode/ 里面项目文件,编译运行,即可在项目根目录生成我们需要的 flatc 工具。也可以使用 cmake 编辑,例如在 Linux 上,运行如下命令即可:

    $ cmake -G "Unix Makefiles"

    $ make

  • 相关阅读:
    java中的Runtime类
    java web 三大核心组件Filter
    springBoot 整合mybaits 逆向工程
    java内存区域之程序计数器
    spring mvc 注解整理(一)
    ELK 日志查询分析nginx日志
    npm 淘宝镜像
    两种网页截图功能
    url 编码解码
    对防火墙的理解
  • 原文地址:https://www.cnblogs.com/keyarchen/p/6061336.html
Copyright © 2020-2023  润新知