Java中的序列化不是很复杂,用的面比较广,很基础的内容,而且平时都会涉及到,所以了解这块内容帮助很大。
序列化主要用在RPC通信,数据存储,还有前后台交互(目前客户端向服务器通讯也都用JSON了),等等等等。像平时用到的服务治理框架、MVC架构,都会涉及到,可能有的时候,被包装很好,以至于用的人都没有感觉到。
序列化的种类。这个所谓的种类,是我自己的笔记里面写的,并不是老师教过来的。是为了方便在学的时候理顺条理。
- 二进制
- JSON
- XML
首先,二进制序列化。是将要序列化的内容(原始类型相当简单,主要是对象),用一定的位编排格式拼装成二进制流的形式。比如对一个对象(包含A、B、C三个属性)序列化,会使用这种方式生成一个二进制数组:类名|参数A类型:参数A的值|参数B类型:参数B的值|参数C类型:参数C的值。而后反序列化,也会使用这个格式,再把这个二进制数组,反序列化成一个对象。
一,二进制序列化的工具有很多种。主要有Java自带的ObjectOutputStream、Hessian、Thrift、Protostuff、ProtocolBuffer、MessagePack、Avro、Kryo等。看起来很乱,没时间的话主要了解前三个就好。
1,Java原生二进制序列化。使用方式如 :
ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(new Person("wang", new Date(), 20));
生成的结果为:
�� sr 'name.gary.vo.Person��� I ageL birthdayt Ljava/util/Date;L namet Ljava/lang/String;xp sr java.util.Datehj�KYt xpw a2�'xt lisi
这个方式支持循环引用。的缺点就是对类修改的适应性太差,有点类改动就出问题。平时两个模块通讯接口类就必须特别严谨,还有就是有时候会导致在消息队列消费时反序列化出错。
2,Hessian二进制序列化。使用方式如:
Hessian2Output hessianOutput = new Hessian2Output(baos); hessianOutput.writeObject(new Person("wang", new Date(), 21));
生成结果为:
C0'name.gary.vo.Person�nameagebirthday`zhangsan�J a2�'
想对比之下,Hessian比原生的序列化结果,简洁很多。可以看得出,属性的类型已经被省略了。可以通过ExtSerializeFactory类进行扩展。Hessian的循环对象引用,是通过在内部维护一个IdentityHashMap的对象池,当遇到已经操作过的对象时,就会使用一种引用方式。
3,Protostuff二进制序列化。使用方式如:
GraphIOUtil.toByteArray(new Person("lisi", new Date(), 20), schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
生成结果为:
lisiy��6a
这个结果比之前的两个,更加简洁,连类名都没有了。但是Protostuff支持循环引用的话,需要设置为graph模式。而且在一个嵌套自引用的反序列化时,Protostuff反序列化出了两个对象,而不是一个对象的引用。
4,MessagePack二进制序列化。使用方法如:
msgpack.register(Person.class);
msgpack.write(new Person("wang", new Date(), 20));
生成结果有:
��lisi� a3���
生成对象时,是需要提前注册的如:msgpack.register(Person.class);而且不支持嵌套引用。
5,Kroyo二进制序列化。使用方法:
kryo.writeObject(output, new Person("zhangsan", new Date(), 20));或
kryo.writeClassAndObject(output, new Person("lisi", new Date(), 20));
生成结果:
( java.util.Dat�̂���,zhangsan�
或
name.gary.vo.Perso�(java.util.Dat��ɵ��,lis�
MessagePack本身支持两种方法,差别从方法名可以看出来。默认支持循环对象引用,不会死循环。而且他默认的序列化编码竟然是Base64。
至于Avro、Thrift、PB,由于其设计是跨语言,需要使用工具生成很多内容。就不再演示。
二,JSON序列化。
这个有点开发经验的人,应该都使用过,业务开发时比较常用。
主要工具有FastJson、Jackson、gson,还有一个JsonLib的包,由于代码维护已经很不活跃且依赖和使用都比较复杂,就不再考虑。
1,fastjson,是阿里开源的一个工具。使用方式:
JSON.toJSONString
一个简单的序列化结果为:
{"age":21,"birthday":1517063537248,"me":{"$ref":"@"},"name":"shangsan","wife":{"age":18,"name":"li"}}
2,Jackson,使用的也比较普遍。向Spring框架默认都会使用此工具。
使用方式:
objectmapper.writeValueAsString(person);
序列化结果为:
{"name":"zhangsan","birthday":null,"age":30,"wife":null,"me":null}
jackson在循环引用时抛错,解决的话需要使用注解JsonIgnoreProperties或关键字transient,人为阻断。但这种方式指标不治本,而且不是我要的结果。
3,Gson,循环运用好像也不行。
使用方式:
gson.toJson(new Person("zhangsan", new Date(), 21));
序列化结果:
{"age":21,"birthday":1517063537248,"me":{"$ref":"@"},"name":"zhangsan","wife":{"age":18,"name":"li"}}
三,XML序列化。
XML前几面是特别火,火到大多数交流都会提到,大多数面试都会问一下这个内容。但是随着Json的出现和大家对简洁配置的推崇,XML现在很少有人再专门提及了。XML的工具,主要的有DOM操作和对象操作两种。
1,DOM操作工具有javax.xml.parsers和org.w3c.dom包小的一些类。使用时,不会面向对象,而是面向XML文档结构,通过getElementsByTagName、getChildNodes、getAttributes、getNodeName、getNodeValue之类的方法,解析出想要的结果。
2,对象操作,就比较直观了。通过注解配置(如javax.xml.bind包下面的JAXB方式),或代码控制(如org.apache.commons.digester3),使用工具将XML文件与对象透明转换,在代码中可以直接操作对象。当然这个方式比DOM操作简单方便一些。