序列化的原理

Java 序列化

  1. Java 序列化只是針對對象的狀態(tài)進(jìn)行保存,至于對象中的方法,序列化不關(guān)心
  2. 當(dāng)一個父類實現(xiàn)了序列化,那么子類會自動實現(xiàn)序列化,不需要顯示實現(xiàn)序列化接口
  3. 當(dāng)一個對象的實例變量引用了其他對象,序列化這個對象的時候會自動把引用的對象也進(jìn)行序列化(實現(xiàn)深度克?。?/li>
  4. 當(dāng)某個字段被申明為 transient 后,默認(rèn)的序列化機(jī)制會忽略這個字段
  5. 被申明為 transient 的字段,如果需要序列化,可以添加兩個私有方法:writeObject 和readObject

各種序列化技術(shù)

XML 序列化

XML 序列化的好處在于可讀性好,方便閱讀和調(diào)試。但是序列化以后的字節(jié)碼文件比較大,而且效率不高,適用于對性能不高,而且 QPS 較低的企業(yè)級內(nèi)部系統(tǒng)之間的數(shù)據(jù)交換的場景,同時 XML 又具有語言無關(guān)性,所以還可以用于異構(gòu)系統(tǒng)之間的數(shù)據(jù)交換和協(xié)議。比如我們熟知的 webservice,就是采用 XML 格式對數(shù)據(jù)進(jìn)行序列化的。XML 序列化/反序列化的實現(xiàn)方式有很多,熟知的方式有 XStream 和 Java 自帶的 XML 序列化和反序列化兩種

JSON 序列化

JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式,相對于 XML 來說,JSON的字節(jié)流更小,而且可讀性也非常好?,F(xiàn)在 JSON 數(shù)據(jù)格式在企業(yè)運用是最普遍的JSON 序列化常用的開源工具有很多

  1. Jackson (https://github.com/FasterXML/jackson
  2. 阿里開源的 FastJson (https://github.com/alibaba/fastjon
  3. Google 的 GSON (https://github.com/google/gson)

這幾種 json 序列化工具中,Jackson 與 fastjson 要比 GSON 的性能要好,但是 Jackson、GSON 的穩(wěn)定性要比 Fastjson 好。而 fastjson 的優(yōu)勢在于提供的 api 非常容易使用

Hessian 序列化

Hessian 是一個支持跨語言傳輸?shù)亩M(jìn)制序列化協(xié)議,相對于 Java 默認(rèn)的序列化機(jī)制來說,Hessian 具有更好的性能和易用性,而且支持多種不同的語言
實際上 Dubbo 采用的就是 Hessian 序列化來實現(xiàn),只不過 Dubbo 對 Hessian 進(jìn)行了重構(gòu),性能更高

Avro 序列化

Avro 是一個數(shù)據(jù)序列化系統(tǒng),設(shè)計用于支持大批量數(shù)據(jù)交換的應(yīng)用。它的主要特點有:支持二進(jìn)制序列化方式,可以便捷,快速地處理大量數(shù)據(jù);動態(tài)語言友好,Avro 提供的機(jī)制使動態(tài)語言可以方便地處理 Avro 數(shù)據(jù)

kyro 序列化

Kryo 是一種非常成熟的序列化實現(xiàn),已經(jīng)在 Hive、Storm)中使用得比較廣泛,不過它不能跨語言. 目前 dubbo 已經(jīng)在 2.6 版本支持 kyro 的序列化機(jī)制。它的性能要優(yōu)于之前的hessian2

Protobuf 序列化

Protobuf 是 Google 的一種數(shù)據(jù)交換格式,它獨立于語言、獨立于平臺。Google 提供了多種語言來實現(xiàn),比如 Java、C、Go、Python,每一種實現(xiàn)都包含了相應(yīng)語言的編譯器和庫文件,Protobuf 是一個純粹的表示層協(xié)議,可以和各種傳輸層協(xié)議一起使用。
Protobuf 使用比較廣泛,主要是空間開銷小和性能比較好,非常適合用于公司內(nèi)部對性能要求高的 RPC 調(diào)用。 另外由于解析性能比較高,序列化以后數(shù)據(jù)量相對較少,所以也可以應(yīng)用在對象的持久化場景中但是要使用 Protobuf 會相對來說麻煩些,因為他有自己的語法,有自己的編譯器,如果需要用到的話必須要去投入成本在這個技術(shù)的學(xué)習(xí)中

protobuf 有個缺點就是要傳輸?shù)拿恳粋€類的結(jié)構(gòu)都要生成對應(yīng)的 proto 文件,如果某個類發(fā)生修改,還得重新生成該類對應(yīng)的 proto 文件

選型建議

  1. 對性能要求不高的場景,可以采用基于 XML 的 SOAP 協(xié)議
  2. 對性能和間接性有比較高要求的場景,那么 Hessian、Protobuf、Thrift、Avro 都可以。
  3. 基于前后端分離,或者獨立的對外的 api 服務(wù),選用 JSON 是比較好的,對于調(diào)試、可讀性都很不錯
  4. Avro 設(shè)計理念偏于動態(tài)類型語言,那么這類的場景使用 Avro 是可以的

各個序列化技術(shù)的性能比較
這個地址有針對不同序列化技術(shù)進(jìn)行性能比較: https://github.com/eishay/jvm-serializers/wiki

Protobuf 序列化的原理

protobuf 的基本應(yīng)用

使用 protobuf 開發(fā)的一般步驟是

  1. 配置開發(fā)環(huán)境,安裝 protocol compiler 代碼編譯器
  2. 編寫.proto 文件,定義序列化對象的數(shù)據(jù)結(jié)構(gòu)
  3. 基于編寫的.proto 文件,使用 protocol compiler 編譯器生成對應(yīng)的序列化/反序列化工具類
  4. 基于自動生成的代碼,編寫自己的序列化應(yīng)用

Protobuf 案例演示

編寫 proto 文件

syntax="proto2";
package com.gupaoedu.serial;
option java_package = 
"com.wei.serial";
option java_outer_classname="UserProtos";
message User {
required string name=1;
required int32 age=2;
}

數(shù)據(jù)類型
string / bytes / bool / int32(4 個字節(jié))
/int64/float/double
enum 枚舉類
message 自定義類
修飾符
required 表示必填字段
optional 表示可選字段
repeated 可重復(fù),表示集合
1,2,3,4 需要在當(dāng)前范圍內(nèi)是唯一的,表示順序

生成實體類

.\protoc.exe --java_out=./user.proto

實現(xiàn)序列化

   public static void main(String[] args) {
        UserProtos.User user = UserProtos.User.newBuilder().setAge(300).setName("Mic").build();
        byte[] bytes = user.toByteArray();
        for (byte bt : bytes) {
            System.out.print(bt + " ");
        }
    }

輸出結(jié)果:10 3 77 105 99 16 -84 2
可以看到,序列化出來的數(shù)字基本看不懂,但是序列化以后的數(shù)據(jù)確實很小,那我們來了解一下底層的原理

正常來說,要達(dá)到最小的序列化結(jié)果,一定會用到壓縮的技術(shù),而 protobuf 里面用到了兩種
壓縮算法,一種是 varint,另一種是 zigzag

-varint
先來看 age=300 這個數(shù)字是如何被壓縮的


image.png

這兩個字節(jié)字節(jié)分別的結(jié)果是:-84 、2
-84 怎么計算來的呢? 我們知道在二進(jìn)制中表示負(fù)數(shù)的方法,高位設(shè)置為 1, 并且是對應(yīng)數(shù)字的二進(jìn)制取反以后再計算補(bǔ)碼表示(補(bǔ)碼是反碼+1)
所以如果要反過來計算

  1. 【補(bǔ)碼】10101100 -1 得到 10101011
  2. 【反碼】01010100 得到的結(jié)果為 84. 由于高位是 1,表示負(fù)數(shù)所以結(jié)果為-84

字符如何轉(zhuǎn)化為編碼
“Mic”這個字符,需要根據(jù) ASCII 對照表轉(zhuǎn)化為數(shù)字。
M =77、i=105、c=99
所以結(jié)果為 77 105 99
這里的結(jié)果為什么直接就是 ASCII 編碼的值呢?怎么沒有做壓縮呢?
原因是,varint 是對字節(jié)碼做壓縮,但是如果這個數(shù)字的二進(jìn)制只需要一個字節(jié)表示的時候,其實最終編碼出來的結(jié)果是不會變化的
還有兩個數(shù)字,3 和 16 代表什么呢?那就要了解 protobuf 的存儲格式了

存儲格式
protobuf 采用 T-L-V 作為存儲方式


image.png

tag 的計算方式是 field_number(當(dāng)前字段的編號) << 3 | wire_type
比如 Mic 的字段編號是 1 ,類型 wire_type 的值為 2 所以 : 1 <<3 | 2 =10
age=300 的字段編號是 2,類型 wire_type 的值是 0, 所以 : 2<<3|0 =16

第一個數(shù)字 10,代表的是 key,剩下的都是 value

負(fù)數(shù)的存儲
在計算機(jī)中,負(fù)數(shù)會被表示為很大的整數(shù),因為計算機(jī)定義負(fù)數(shù)符號位為數(shù)字的最高位,所以如果采用 varint 編碼表示一個負(fù)數(shù),那么一定需要 5 個比特位。所以在 protobuf 中通過sint32/sint64 類型來表示負(fù)數(shù),負(fù)數(shù)的處理形式是先采用 zigzag 編碼(把符號數(shù)轉(zhuǎn)化為無符號數(shù)),再采用 varint 編碼。

sint32:(n << 1) ^ (n >> 31)
sint64:(n << 1) ^ (n >> 63)
比如存儲一個(-300)的值
-300
原碼:0001 0010 1100
取反:1110 1101 0011
加 1 :1110 1101 0100
n<<1: 整體左移一位,右邊補(bǔ) 0 -> 1101 1010 1000
n>>31: 整體右移 31 位,左邊補(bǔ) 1 -> 1111 1111 1111
n<<1 ^ n >>31
1101 1010 1000 ^ 1111 1111 1111 = 0010 0101 0111
十進(jìn)制: 0010 0101 0111 = 599
varint 算法: 從右往做,選取 7 位,高位補(bǔ) 1/0(取決于字節(jié)數(shù))
得到兩個字節(jié)
1101 0111 和 0000 0100
-41 和 4

Protocol Buffer 的性能好,主要體現(xiàn)在 序列化后的數(shù)據(jù)體積小 & 序列化速度快,最終使得傳輸效率高,其原因如下:

  • 序列化速度快的原因:
    a. 編碼 / 解碼 方式簡單(只需要簡單的數(shù)學(xué)運算 = 位移等等)
    b. 采用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成

  • 序列化后的數(shù)據(jù)量體積小(即數(shù)據(jù)壓縮效果好)的原因:
    a. 采用了獨特的編碼方式,如 Varint、Zigzag 編碼方式等等
    b. 采用 T - L - V 的數(shù)據(jù)存儲方式:減少了分隔符的使用 & 數(shù)據(jù)存儲得緊湊

    ——學(xué)自咕泡學(xué)院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內(nèi)容