• Hessian 序列化、反序列化


    动手点关注干货不迷路 👆

    背景

    问题和思考:

    1. 序列化参数有枚举属性,序列化端增加一个枚举,能否正常反序列化?

    2. 序列化子类,它和父类有同名参数,反序列化时,同名参数能否能正常赋值?

    3. 序列化对象增加参数,反序列化类不增加参数,能否正常反序列化?

    4. 用于序列化传输的属性,用包装器比较好,还是基本类型比较好?

    为什么要使用序列化和反序列化

    1. 程序在运行过程中,产生的数据,不能一直保存在内存中,需要暂时或永久存储到介质(如磁盘、数据库、文件)里进行保存,也可能通过网络发送给协作者。程序获取原数据,需要从介质,或网络传输获得。传输的过程中,只能使用二进制流进行传输。

    2. 简单的场景,基本类型数据传输。通过双方约定好参数类型,数据接收方按照既定规则对二进制流进行反序列化。

    5728ae10eac022ba36bd6c40293445a7.png
    1. 复杂的场景,传输数据的参数类型可能包括:基本类型、包装器类型、自定义类、枚举、时间类型、字符串、容器等。很难简单通过约定来反序列化二进制流。需要一个通用的协议,共双方使用,进行序列化和反序列化。

    三种序列化协议及对比

    序列化协议特点
    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

    对比

    1. Father father = new Father();
    2. father.name = "厨师";
    3. father.comment = "川菜馆";
    4. father.simpleInt = 1;
    5. father.boxInt = new Integer(10);
    6. father.simpleDouble = 1;
    7. father.boxDouble = new Double(10);
    8. father.bigDecimal = new BigDecimal(11.5);

    运行结果:

    1. jdk序列化结果长度:626,耗时:55
    2. jdk反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:87
    3. hessian序列化结果长度:182,耗时:56
    4. hessian反序列化结果:Father{version=0, name='厨师', comment='川菜馆', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗时:7
    5. Fastjson序列化结果长度:119,耗时:225
    6. 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 序列化实战

    实验准备

    父类

    1. public class Father implements Serializable {
    2.     /**
    3.      * 静态类型不会被序列化
    4.      */
    5.     private static final long serialVersionUID = 1L;
    6.     /**
    7.      * transient 不会被序列化
    8.      */
    9.     transient int version = 0;
    10.     /**
    11.      * 名称
    12.      */
    13.     public String name;
    14.     /**
    15.      * 备注
    16.      */
    17.     public String comment;
    18.     /**
    19.      * 包装器类型1
    20.      */
    21.     public Integer boxInt;
    22.     /**
    23.      * 基本类型1
    24.      */
    25.     public int simpleInt;
    26.     /**
    27.      * 包装器类型2
    28.      */
    29.     public Double boxDouble;
    30.     /**
    31.      * 基本类型2
    32.      */
    33.     public double simpleDouble;
    34.     /**
    35.      * BigDecimal
    36.      */
    37.     public BigDecimal bigDecimal;
    38.     public Father() {
    39.     }
    40.     @Override
    41.     public String toString() {
    42.         return "Father{" +
    43.                 "version=" + version +
    44.                 ", name='" + name + '\'' +
    45.                 ", comment='" + comment + '\'' +
    46.                 ", boxInt=" + boxInt +
    47.                 ", simpleInt=" + simpleInt +
    48.                 ", boxDouble=" + boxDouble +
    49.                 ", simpleDouble=" + simpleDouble +
    50.                 ", bigDecimal=" + bigDecimal +
    51.                 '}';
    52.     }
    53. }

    子类

    1. public class Son extends Father {
    2.     /**
    3.      * 名称,与father同名属性
    4.      */
    5.     public String name;
    6.     /**
    7.      * 自定义类
    8.      */
    9.     public Attributes attributes;
    10.     /**
    11.      * 枚举
    12.      */
    13.     public Color color;
    14.     public Son() {
    15.     }
    16. }

    属性-自定义类

    1. public class Attributes implements Serializable {
    2.     private static final long serialVersionUID = 1L;
    3.     public int value;
    4.     public String msg;
    5.     public Attributes() {
    6.     }
    7.     public Attributes(int value, String msg) {
    8.         this.value = value;
    9.         this.msg = msg;
    10.     }
    11. }

    枚举

    1. public enum Color {
    2.     RED(1"red"),
    3.     YELLOW(2"yellow")
    4.     ;
    5.     public int value;
    6.     public String msg;
    7.     Color() {
    8.     }
    9.     Color(int value, String msg) {
    10.         this.value = value;
    11.         this.msg = msg;
    12.     }
    13. }

    使用到的对象及属性设置

    1. Son son = new Son();
    2. son.name = "厨师";    // 父子类同名字段,只给子类属性赋值
    3. son.comment = "川菜馆";
    4. son.simpleInt = 1;
    5. son.boxInt = new Integer(10);
    6. son.simpleDouble = 1;
    7. son.boxDouble = new Double(10);
    8. son.bigDecimal = new BigDecimal(11.5);
    9. son.color = Color.RED;
    10. son.attributes = new Attributes(11"hello");

    运行结果分析

    使用 Hessian 序列化,结果写入文件,使用 vim 打开。使用 16 进制方式查看,查看命令:%!xxd

    1. 000000004307 6474 6f2e 5366e90466166504  C.dto.Son..name.
    2. 000000106e61 6d65 0763 6f6d 6d65 6e74 0662 6f78  name.comment.box
    3. 000000204967409 7369 6d70 6c65 4967409 626f  Int.simpleInt.bo
    4. 000000307844 6f75 6266507369 6d70 6c65 446f  xDouble.simpleDo
    5. 000000407562 6c65 0a61 7474 7269 6275 7465 7305  uble.attributes.
    6. 000000506366c6f 7206269 6744 6563 696616c  color.bigDecimal
    7. 000000606002 e58e a8e5 b888 4e03 e5b7 9de8 8f9c  `.......N.......
    8. 00000070: e9a6 869a 915d 0a5c 430e 6474 6f2e 4174  .....].\C.dto.At
    9. 00000080: 7472 6962 7574 6573 9205 7661 6c75 6503  tributes..value.
    10. 00000090: 6d73 6761 9b05 6865 6c6c 6f43 0964 746f  msga..helloC.dto
    11. 000000a0: 2e43 6f6c 6f72 9104 6e61 6d65 6203 5245  .Color..nameb.RE
    12. 000000b0: 4443 146a 6176 612e 6d61 7468 2e42 6967  DC.java.math.Big
    13. 000000c0: 4465 6369 6d61 6c91 0576 616c 7565 6304  Decimal..valuec.
    14. 000000d0: 3131 2e35 0a                             11.5.

    对其中的十六进制数逐个分析,可以拆解为一下结构:参考 hessian 官方文档,链接:http://hessian.caucho.com/doc/hessian-serialization.html

    序列化原理

    0eb23d45fb9c847d9bad93bf6d9ecaf2.png

    序列化规则:

    1. 被序列化的类必须实现了 Serializable 接口

    6df3aa0f418f1a76eda1634f6b79d58a.png
    1. 静态属性和 transient 变量,不会被序列化。

    deba092923ccfbb4a694da3e7fccc1f9.png
    1. 枚举类型在序列化后,存储的是枚举变量的名字

    2. 序列化结果的结构:类定义开始标识 C -> 类名长度+类名 -> 属性数量 -> (逐个)属性名长度+属性名 -> 开始实例化标识 -> (按照属性名顺序,逐个设置)属性值(发现某个属性是一个对象,循环这个过程)

    6f9412add07aa335ee1560f3e89b8f67.png

    反序列化

    7bb1923c2f9489224840091738edb48d.png

    通俗原理图:

    fc59df4ed46caf7db07c05be9d1f764a.png db51e65bfaaf7cbac875cee592aa58ed.png

    解释:这是前边的序列化文件,可以对着这个结构理解反序列化的过程。

    87c71c4a775ed2eba0c94ce72bed61a1.png

    解释:读取到“C”之后,它就知道接下来是一个类的定义,接着就开始读取类名,属性个数和每个属性的名称。并把这个类的定义缓存到一个_classDefs 的 list 里。

    76f72ea1da43c8df1e1b0682646371ff.png

    解释:通过读取序列化文件,获得类名后,会加载这个类,并生成这个类的反序列化器。这里会生成一个_fieldMap,key 为反序列化端这个类所有属性的名称,value 为属性对应的反序列化器。

    356977d3416290f72b3da67fc7514c46.png

    解释:读到 6 打头的 2 位十六进制数时,开始类的实例化和赋值。

    遗留问题解答:

    • 增加枚举类型,反序列化不能正常读取。

    ca3e7a44b0651c1d0c205f544c8d9703.png
      • 原因:枚举类型序列化结果中,枚举属性对应的值是枚举名。反序列化时,通过枚举类类名+枚举名反射生成枚举对象。枚举名找不到就会报错。

    • 反序列化为子类型,同名属性值无法正常赋值。

    e5b5c0e5d511fd4a43b1d4ec163dbf67.png 30b399d8cbc0cc9f26045e1a4f31fc5a.png d70f1c6892a81054cacf03d60acab35a.png
    • 序列化对象增加参数,反序列化可以正常运行。

    d3321fef094118e68bead78e8cd69904.png
      • 原因:反序列化时,是先通过类名加载同名类,并生成同名类的反序列化器,同名类每个属性对应的反序列化器存储在一个 map 中。在反序列化二进制文件时,通过读取到的属性名,到 map 中获取对应的反序列化器。若获取不到,默认是 NullFieldDeserializer.DESER。待到读值的时候,仅读值,不作 set 操作

    • 序列化和反序列化双方都使用对象类型时,更改属性类型,若序列化方不传输数据,序列化结果是‘N’,能正常反序列化。但是对于一方是基本类型,更改属性类型后,因为 hessian 对于基本类型使用不同范围的值域,所以无法正常序列化。


    参考文档:

    • https://zhuanlan.zhihu.com/p/44787200

    • https://paper.seebug.org/1131/

    • hessian 官方文档:序列化规则http://hessian.caucho.com/doc/hessian-serialization.html#anchor10

    • ASCII 编码对照表http://ascii.911cha.com/

  • 相关阅读:
    基于SSH的宿舍管理系统
    PCL ICP点云精配准(点到面)
    设备树和设备树语法
    python3-循环与条件语句
    Ray tracing 光线追踪 之 embree ,从入门到精通 01 安装与体验
    运动装备哪些品牌好?运动装备好物推荐
    JavaScript:实现使用 2 个堆栈形成队列算法(附完整源码)
    三剑客-shell篇(讲解贼详细)
    国科大图数据管理与分析课程项目gStore实验报告
    java计算机毕业设计阅读与存储图书网站设计与实现源码+系统+mysql数据库+lw文档+部署
  • 原文地址:https://blog.csdn.net/ByteDanceTech/article/details/126188189