背景
問題和思考:
- 序列化參數(shù)有枚舉屬性,序列化端增加一個(gè)枚舉,能否正常反序列化?
- 序列化子類,它和父類有同名參數(shù),反序列化時(shí),同名參數(shù)能否能正常賦值?
- 序列化對(duì)象增加參數(shù),反序列化類不增加參數(shù),能否正常反序列化?
- 用于序列化傳輸?shù)膶傩?,用包裝器比較好,還是基本類型比較好?
為什么要使用序列化和反序列化
- 程序在運(yùn)行過程中,產(chǎn)生的數(shù)據(jù),不能一直保存在內(nèi)存中,需要暫時(shí)或永久存儲(chǔ)到介質(zhì)(如磁盤、數(shù)據(jù)庫、文件)里進(jìn)行保存,也可能通過網(wǎng)絡(luò)發(fā)送給協(xié)作者。程序獲取原數(shù)據(jù),需要從介質(zhì),或網(wǎng)絡(luò)傳輸獲得。傳輸?shù)倪^程中,只能使用二進(jìn)制流進(jìn)行傳輸。
- 簡單的場(chǎng)景,基本類型數(shù)據(jù)傳輸。通過雙方約定好參數(shù)類型,數(shù)據(jù)接收方按照既定規(guī)則對(duì)二進(jìn)制流進(jìn)行反序列化。
- 復(fù)雜的場(chǎng)景,傳輸數(shù)據(jù)的參數(shù)類型可能包括:基本類型、包裝器類型、自定義類、枚舉、時(shí)間類型、字符串、容器等。很難簡單通過約定來反序列化二進(jìn)制流。需要一個(gè)通用的協(xié)議,共雙方使用,進(jìn)行序列化和反序列化。
三種序列化協(xié)議及對(duì)比
序列化協(xié)議 | 特點(diǎn) |
jdk | 1. 序列化:除了 static、transient類型 |
fastjson | 1. 可讀性好,空間占用小 |
hessian | 1. 序列化:除了 static、transient 類型 |
對(duì)比
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);
運(yùn)行結(jié)果:
jdk序列化結(jié)果長度:626,耗時(shí):55
jdk反序列化結(jié)果:Father{version=0, name='廚師', comment='川菜館', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗時(shí):87
hessian序列化結(jié)果長度:182,耗時(shí):56
hessian反序列化結(jié)果:Father{version=0, name='廚師', comment='川菜館', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗時(shí):7
Fastjson序列化結(jié)果長度:119,耗時(shí):225
Fastjson反序列化結(jié)果:Father{version=0, name='廚師', comment='川菜館', boxInt=10, simpleInt=1, boxDouble=10.0, simpleDouble=1.0, bigDecimal=11.5}耗時(shí):69
分析:
- jdk 序列化耗時(shí)最短,但是序列化結(jié)果長度最長,是其它兩種的 3 ~ 5 倍。
- fastjson 序列化結(jié)果長度最短,但是耗時(shí)是其它兩種的 4 倍左右。
- hessian 序列化耗時(shí)與 jdk 差別不大,遠(yuǎn)小于 fastjson 序列化耗時(shí)。且與 jdk 相比,序列化結(jié)果占用空間非常有優(yōu)勢(shì)。另外,hessian 的反序列化速度最快,耗時(shí)是其它兩種的 1/10。
- 綜上比較,hessian 在序列化和反序列化表現(xiàn)中,性能最優(yōu)。
Hessian 序列化實(shí)戰(zhàn)
實(shí)驗(yàn)準(zhǔn)備
父類
public class Father implements Serializable {
/**
* 靜態(tài)類型不會(huì)被序列化
*/
private static final long serialVersionUID = 1L;
/**
* transient 不會(huì)被序列化
*/
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;
}
}
使用到的對(duì)象及屬性設(shè)置
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");
運(yùn)行結(jié)果分析
使用 Hessian 序列化,結(jié)果寫入文件,使用 vim 打開。使用 16 進(jìn)制方式查看,查看命令:%!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.
對(duì)其中的十六進(jìn)制數(shù)逐個(gè)分析,可以拆解為一下結(jié)構(gòu):參考 hessian 官方文檔,鏈接:http://hessian.caucho.com/doc/hessian-serialization.html
序列化原理
序列化規(guī)則:
- 被序列化的類必須實(shí)現(xiàn)了 Serializable 接口
- 靜態(tài)屬性和 transient 變量,不會(huì)被序列化。
- 枚舉類型在序列化后,存儲(chǔ)的是枚舉變量的名字
- 序列化結(jié)果的結(jié)構(gòu):類定義開始標(biāo)識(shí) C -> 類名長度+類名 -> 屬性數(shù)量 -> (逐個(gè))屬性名長度+屬性名 -> 開始實(shí)例化標(biāo)識(shí) -> (按照屬性名順序,逐個(gè)設(shè)置)屬性值(發(fā)現(xiàn)某個(gè)屬性是一個(gè)對(duì)象,循環(huán)這個(gè)過程)
反序列化
通俗原理圖:
解釋:這是前邊的序列化文件,可以對(duì)著這個(gè)結(jié)構(gòu)理解反序列化的過程。
解釋:讀取到“C”之后,它就知道接下來是一個(gè)類的定義,接著就開始讀取類名,屬性個(gè)數(shù)和每個(gè)屬性的名稱。并把這個(gè)類的定義緩存到一個(gè)_classDefs 的 list 里。
解釋:通過讀取序列化文件,獲得類名后,會(huì)加載這個(gè)類,并生成這個(gè)類的反序列化器。這里會(huì)生成一個(gè)_fieldMap,key 為反序列化端這個(gè)類所有屬性的名稱,value 為屬性對(duì)應(yīng)的反序列化器。
解釋:讀到 6 打頭的 2 位十六進(jìn)制數(shù)時(shí),開始類的實(shí)例化和賦值。
遺留問題解答:
- 增加枚舉類型,反序列化不能正常讀取。
- 原因:枚舉類型序列化結(jié)果中,枚舉屬性對(duì)應(yīng)的值是枚舉名。反序列化時(shí),通過枚舉類類名+枚舉名反射生成枚舉對(duì)象。枚舉名找不到就會(huì)報(bào)錯(cuò)。
- 反序列化為子類型,同名屬性值無法正常賦值。
- 序列化對(duì)象增加參數(shù),反序列化可以正常運(yùn)行。
原因:反序列化時(shí),是先通過類名加載同名類,并生成同名類的反序列化器,同名類每個(gè)屬性對(duì)應(yīng)的反序列化器存儲(chǔ)在一個(gè) map 中。在反序列化二進(jìn)制文件時(shí),通過讀取到的屬性名,到 map 中獲取對(duì)應(yīng)的反序列化器。若獲取不到,默認(rèn)是 NullFieldDeserializer.DESER。待到讀值的時(shí)候,僅讀值,不作 set 操作
- 序列化和反序列化雙方都使用對(duì)象類型時(shí),更改屬性類型,若序列化方不傳輸數(shù)據(jù),序列化結(jié)果是‘N’,能正常反序列化。但是對(duì)于一方是基本類型,更改屬性類型后,因?yàn)?hessian 對(duì)于基本類型使用不同范圍的值域,所以無法正常序列化。