背景
问题和思考:
-
序列化参数有枚举属性,序列化端增加一个枚举,能否正常反序列化?
序列化子类,它和父类有同名参数,反序列化时,同名参数能否能正常赋值?
序列化对象增加参数,反序列化类不增加参数,能否正常反序列化?
用于序列化传输的属性,用包装器比较好,还是基本类型比较好?
为什么要使用序列化和反序列化
程序在运行过程中,产生的数据,不能一直保存在内存中,需要暂时或永久存储到介质(如磁盘、数据库、文件)里进行保存,也可能通过网络发送给协作者。程序获取原数据,需要从介质,或网络传输获得。传输的过程中,只能使用二进制流进行传输。 简单的场景,基本类型数据传输。通过双方约定好参数类型,数据接收方按照既定规则对二进制流进行反序列化。 复杂的场景,传输数据的参数类型可能包括:基本类型、包装器类型、自定义类、枚举、时间类型、字符串、容器等。很难简单通过约定来反序列化二进制流。需要一个通用的协议,共双方使用,进行序列化和反序列化。三种序列化协议及对比
序列化协议 | 特点 |
jdk (jdk 自带) | 1. 序列化:除了 static、transient类型 2. 特点:强类型,安全性高,序列化结果携带类型信息 3. 反序列化:基于 Field 机制 4. 应用场景:深拷贝 |
fastjson (第三方实现) | 1. 可读性好,空间占用小 2. 特点:弱类型,序列化结果不携带类型信息,可读性强。有一些安全性问题 3. 反序列化:基于 Field 机制,兼容 Bean 机制 4. 应用场景:消息、透传对象 |
hessian (第三方实现) | 1. 序列化:除了 static、transient 类型 2. 特点:强类型,体积小,可跨语言,序列化结果携带类型信息 3. 反序列化:基于 Field 机制,兼容 Bean 机制 4. 应用场景:RPC |
对比
Father father = new Father(); father.name = "厨师"; father.comment = "川菜馆"; father.simpleInt = 1; father.boxInt = new Integer(10); father.simpleDouble = 1; father.boxDouble = new Double(10); father.bigDecimal = new BigDecimal(11.5);
运行结果:
jdk序列化结果长度:626,耗时:55 jdk反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:87 hessian序列化结果长度:182,耗时:56 hessian反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:7 Fastjson序列化结果长度:119,耗时:225 Fastjson反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:69
分析:
-
jdk 序列化耗时最短,但是序列化结果长度最长,是其它两种的 3 ~ 5 倍。
fastjson 序列化结果长度最短,但是耗时是其它两种的 4 倍左右。
hessian 序列化耗时与 jdk 差别不大,远小于 fastjson 序列化耗时。且与 jdk 相比,序列化结果占用空间非常有优势。另外,hessian 的反序列化速度最快,耗时是其它两种的 1/10。
综上比较,hessian 在序列化和反序列化表现中,性能最优。
Hessian 序列化实战
实验准备
父类
public class Father implements Serializable { /** * 静态类型不会被序列化 */ private static final long serialVersionUID = 1L; /** * transient 不会被序列化 */ transient int version = 0; /** * 名称 */ public String name; /** * 备注 */ public String comment; /** * 包装器类型1 */ public Integer boxInt; /** * 基本类型1 */ public int simpleInt; /** * 包装器类型2 */ public Double boxDouble; /** * 基本类型2 */ public double simpleDouble; /** * BigDecimal */ public BigDecimal bigDecimal; public Father() { } @Override public String toString() { return "Father{" + "version=" + version + ", name='" + name + '\'' + ", comment='" + comment + '\'' + ", boxInt=" + boxInt + ", simpleInt=" + simpleInt + ", boxDouble=" + boxDouble + ", simpleDouble=" + simpleDouble + ", bigDecimal=" + bigDecimal + '}'; } }
子类
public class Son extends Father { /** * 名称,与father同名属性 */ public String name; /** * 自定义类 */ public Attributes attributes; /** * 枚举 */ public Color color; public Son() { } }
属性-自定义类
public class Attributes implements Serializable { private static final long serialVersionUID = 1L; public int value; public String msg; public Attributes() { } public Attributes(int value, String msg) { this.value = value; this.msg = msg; } }
枚举
public enum Color { RED(1, "red"), YELLOW(2, "yellow") ; public int value; public String msg; Color() { } Color(int value, String msg) { this.value = value; this.msg = msg; } }
使用到的对象及属性设置
Son son = new Son(); son.name = "厨师"; // 父子类同名字段,只给子类属性赋值 son.comment = "川菜馆"; son.simpleInt = 1; son.boxInt = new Integer(10); son.simpleDouble = 1; son.boxDouble = new Double(10); son.bigDecimal = new BigDecimal(11.5); son.color = Color.RED; son.attributes = new Attributes(11, "hello");
运行结果分析
使用 Hessian 序列化,结果写入文件,使用 vim 打开。使用 16 进制方式查看,查看命令:%!xxd
00000000: 4307 6474 6f2e 536f 6e9a 046e 616d 6504 C.dto.Son..name. 00000010: 6e61 6d65 0763 6f6d 6d65 6e74 0662 6f78 name.comment.box 00000020: 496e 7409 7369 6d70 6c65 496e 7409 626f Int.simpleInt.bo 00000030: 7844 6f75 626c 650c 7369 6d70 6c65 446f xDouble.simpleDo 00000040: 7562 6c65 0a61 7474 7269 6275 7465 7305 uble.attributes. 00000050: 636f 6c6f 720a 6269 6744 6563 696d 616c color.bigDecimal 00000060: 6002 e58e a8e5 b888 4e03 e5b7 9de8 8f9c `.......N....... 00000070: e9a6 869a 915d 0a5c 430e 6474 6f2e 4174 .....].\C.dto.At 00000080: 7472 6962 7574 6573 9205 7661 6c75 6503 tributes..value. 00000090: 6d73 6761 9b05 6865 6c6c 6f43 0964 746f msga..helloC.dto 000000a0: 2e43 6f6c 6f72 9104 6e61 6d65 6203 5245 .Color..nameb.RE 000000b0: 4443 146a 6176 612e 6d61 7468 2e42 6967 DC.java.math.Big 000000c0: 4465 6369 6d61 6c91 0576 616c 7565 6304 Decimal..valuec. 000000d0: 3131 2e35 0a 11.5.
对其中的十六进制数逐个分析,可以拆解为一下结构:参考 hessian 官方文档,链接:http://hessian.caucho.com/doc/hessian-serialization.html
序列化原理
序列化规则:
被序列化的类必须实现了 Serializable 接口 静态属性和 transient 变量,不会被序列化。 枚举类型在序列化后,存储的是枚举变量的名字 序列化结果的结构:类定义开始标识 C -> 类名长度+类名 -> 属性数量 -> (逐个)属性名长度+属性名 -> 开始实例化标识 -> (按照属性名顺序,逐个设置)属性值(发现某个属性是一个对象,循环这个过程)反序列化
通俗原理图: 解释:这是前边的序列化文件,可以对着这个结构理解反序列化的过程。 解释:读取到“C”之后,它就知道接下来是一个类的定义,接着就开始读取类名,属性个数和每个属性的名称。并把这个类的定义缓存到一个_classDefs 的 list 里。 解释:通过读取序列化文件,获得类名后,会加载这个类,并生成这个类的反序列化器。这里会生成一个_fieldMap,key 为反序列化端这个类所有属性的名称,value 为属性对应的反序列化器。 解释:读到 6 打头的 2 位十六进制数时,开始类的实例化和赋值。
遗留问题解答:
-
增加枚举类型,反序列化不能正常读取。
-
原因:枚举类型序列化结果中,枚举属性对应的值是枚举名。反序列化时,通过枚举类类名+枚举名反射生成枚举对象。枚举名找不到就会报错。
-
反序列化为子类型,同名属性值无法正常赋值。
-
序列化对象增加参数,反序列化可以正常运行。
原因:反序列化时,是先通过类名加载同名类,并生成同名类的反序列化器,同名类每个属性对应的反序列化器存储在一个 map 中。在反序列化二进制文件时,通过读取到的属性名,到 map 中获取对应的反序列化器。若获取不到,默认是 NullFieldDeserializer.DESER。待到读值的时候,仅读值,不作 set 操作
-
序列化和反序列化双方都使用对象类型时,更改属性类型,若序列化方不传输数据,序列化结果是‘N’,能正常反序列化。但是对于一方是基本类型,更改属性类型后,因为 hessian 对于基本类型使用不同范围的值域,所以无法正常序列化。
转载请注明:IT运维空间 » 运维技术 » Hessian 序列化、反序列化
发表评论