Hessian 序列化、反序列化

背景

問題和思考:

  1. 序列化參數有枚舉屬性,序列化端增加一個枚舉,能否正常反序列化?

  2. 序列化子類,它和父類有同名參數,反序列化時,同名參數能否能正常賦值?

  3. 序列化對象增加參數,反序列化類不增加參數,能否正常反序列化?

  4. 用於序列化傳輸的屬性,用包裝器比較好,還是基本類型比較好?

爲什麼要使用序列化和反序列化

  1. 程序在運行過程中,產生的數據,不能一直保存在內存中,需要暫時或永久存儲到介質(如磁盤、數據庫、文件)裏進行保存,也可能通過網絡發送給協作者。程序獲取原數據,需要從介質,或網絡傳輸獲得。傳輸的過程中,只能使用二進制流進行傳輸。

  2. 簡單的場景,基本類型數據傳輸。通過雙方約定好參數類型,數據接收方按照既定規則對二進制流進行反序列化。

  1. 複雜的場景,傳輸數據的參數類型可能包括:基本類型、包裝器類型、自定義類、枚舉、時間類型、字符串、容器等。很難簡單通過約定來反序列化二進制流。需要一個通用的協議,共雙方使用,進行序列化和反序列化。

三種序列化協議及對比

wfECwO

對比

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

分析:

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 + '\'' +
                ", 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

序列化原理

序列化規則:

  1. 被序列化的類必須實現了 Serializable 接口

  1. 靜態屬性和 transient 變量,不會被序列化。

  1. 枚舉類型在序列化後,存儲的是枚舉變量的名字

  2. 序列化結果的結構:類定義開始標識 C -> 類名長度 + 類名 -> 屬性數量 -> (逐個)屬性名長度 + 屬性名 -> 開始實例化標識 -> (按照屬性名順序,逐個設置)屬性值(發現某個屬性是一個對象,循環這個過程)

反序列化

通俗原理圖:

解釋:這是前邊的序列化文件,可以對着這個結構理解反序列化的過程。

解釋:讀取到 “C” 之後,它就知道接下來是一個類的定義,接着就開始讀取類名,屬性個數和每個屬性的名稱。並把這個類的定義緩存到一個_classDefs 的 list 裏。

解釋:通過讀取序列化文件,獲得類名後,會加載這個類,並生成這個類的反序列化器。這裏會生成一個_fieldMap,key 爲反序列化端這個類所有屬性的名稱,value 爲屬性對應的反序列化器。

解釋:讀到 6 打頭的 2 位十六進制數時,開始類的實例化和賦值。

遺留問題解答:

參考文檔:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JGQuAln23dm90W9w7QwhDw