天天看點

源碼分析Dubbo序列化-源碼分析kryo序列化實作原理

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

本文主要梳理 Kryo 序列化基本實作。重點剖析 Kryo # writeClassAndObject、Kryo # readClassAndObject 方法。

1、源碼分析Kryo#writeClassAndObject

public void writeClassAndObject (Output output, Object object) {
    if (output == null) throw new IllegalArgumentException("output cannot be null.");
    beginObject();                                             // @1
    try {
        if (object == null) {
            writeClass(output, null);              // @2
            return;
        }
        Registration registration = writeClass(output, object.getClass());   // @3
        if (references && writeReferenceOrNull(output, object, false)) {    // @4
            registration.getSerializer().setGenerics(this, null);
            return;
        }
        if (TRACE || (DEBUG && depth == 1)) log("Write", object);
        registration.getSerializer().write(this, output, object);  // @5
    } finally {
        if (--depth == 0 && autoReset) reset();       // @6
    }
}           

代碼@1:開始序列化,将dept自增,表示目前深度,因為在序列化一個對象時,該方法有可能會被遞歸調用,每遞歸調用增加1,一次調用結束後在finally字句中自減。

代碼@2:如果對象為空,則調用 writeClass ( DefaultSerializers $ ClassSerializer ),序列化為空。

代碼@3:如果對象不為空,首先序列化對象所屬的 Class 執行個體,從這裡可以看出,Kryo 在序列化時,首先先序列化類型。

代碼@4:如果 references 為 true (預設為 true,可以序列化循環依賴),則調用 writeReferenceOrNull 序列化。

代碼@5:如果 references 為 false,則調用 write 序列化,此時如果對象存在循環依賴,則會抛出 throw new KryoException("Max depth exceeded: " + depth ) 異常,如果 object 為基本類型,也将通過該方法完成值的序列化。

代碼@6:完成序列化後,恢複相關資料。也就是說 Kryo 執行個體并不是線程安全的。

預設 references 為 true,表示支援循環嵌套,我們接下來重點跟蹤一下 writeReferenceOrNull 方法。

1.1 源碼分析writeReferenceOrNull方法

/** @param object May be null if mayBeNull is true.
     * @return true if no bytes need to be written for the object. */
    boolean writeReferenceOrNull (Output output, Object object, boolean mayBeNull) {     // @1
        if (object == null) {                                                                                                    // @2                    
            if (TRACE || (DEBUG && depth == 1)) log("Write", null);
            output.writeVarInt(Kryo.NULL, true);
            return true;
        }
        if (!referenceResolver.useReferences(object.getClass())) {                                  // @3
            if (mayBeNull) output.writeVarInt(Kryo.NOT_NULL, true);
            return false;
        }

        // Determine if this object has already been seen in this object graph.
        int id = referenceResolver.getWrittenId(object);                                                 // @4

        // If not the first time encountered, only write reference ID.
        if (id != -1) {                                                                                                        // @5
            if (DEBUG) debug("kryo", "Write object reference " + id + ": " + string(object));
            output.writeVarInt(id + 2, true); // + 2 because 0 and 1 are used for NULL and NOT_NULL.
            return true;
        }

        // Otherwise write NOT_NULL and then the object bytes.
        id = referenceResolver.addWrittenObject(object);     // @6
        output.writeVarInt(NOT_NULL, true);
        if (TRACE) trace("kryo", "Write initial object reference " + id + ": " + string(object));
        return false;                                                              // @7
    }           

代碼@1:參數說明:Output output:輸出流;Object object:待序列化的對象;

代碼@2:如果對象為空,寫入 Kryo.NULL(0),然後傳回 true,表示需要設定 generic,後續會講解一下 generic(泛型支援)。

代碼@3:如果是基本類型,如果 maybe (值可能為空),但該方法不為空,則設定為 Kryo.NOT_NULL ,然後傳回 false,表示非引用類型,需要持久化值。

代碼@4:判斷該對象是否在對象圖中已被序列化一次。(其實作方式 ListReferenceResolver、MapReferenceResolver)。

源碼分析Dubbo序列化-源碼分析kryo序列化實作原理

代碼@5:如果 writtenId 不等于 -1,表示該對象已被序列化,直接序列化 ID,直接傳回 true,然後結束 writeClassAndObject該方法,表示該對象執行個體完成。

代碼@6:為 object 建構一 個 ID,這個 ID 資料是在一次嵌套調用 writeClassAndObject 内有效,然後 writeClassAndObject 結束後,會調用 reset 方法,将其清空,然後先寫入為空辨別,并傳回 false,也就是第一次序列化對象時,傳回 false,會進入到 writeClassAndObject 的代碼@5中。

Kryo#writeClassAndObject

代碼@5

registration.getSerializer().write(this, output, object); // @5

複制代碼

實其重點關鍵,還是 writeClassAndObject # writeClass 也就是上文說的代碼 @3,在序列化對象之前,首先先序列化該對象的類型,然後需要傳回對應的字段序列器。例如,如果類的類型為 java.util.Map,則首先先要記錄類型為 Map,然後傳回可以序列化 Map 的序列器,再例如類型如果是 java.lang.String,則先序列化類型,然後序列化值,序列化值的序列器則為 DefaultSerializers$StringSerializer,那如果是一個對象類型,例如 cn.uce.demo.Student,自然,第一步是先序列化類型 cn.uce.demo.Student,接下來就需要序列化 Student 的各個字段的資訊,傳回的序列化為 DefaultSerializers$FieldSerializer,然後可以通過 FieldSeriaizer 傳回 Student 的屬性清單,然後單獨一個字段一個字段的序列化,其順序也就是,先類型,再序列化值。這樣就遞歸完成了一個對象的序列化操作。

Kryo序列化實作原理:

1、先序列化類型(Class執行個體),然後根據類型傳回相應的序列化器(上一篇詳細介紹了各種類型的序列化器)。

2、再序列化該類型的值。

3、如果自定義類型,例如(cn.uce.demo.Student),則傳回的值序列化器為DefaultSerializers$FieldSerializer,然後一個字段一個字段的序列化,當然其序列化類型也是,先類型再值的模式,遞歸進行,最終完成。

4、引入了對象圖的概念來消除循環依懶的序列化,已序列化的對象,在循環引用時,隻是用一個int類型來表示該對象值,類似一種緩存的概念。

Kryo與java 序列化的差別

kryo 的設計目的是指對象值的序列化,關注的是資料有效資料的傳輸,減少需要序列化的中繼資料資訊。

這一點通過 Kryo 對 Class 對象的序列化,也就是類型的序列化就能看出端倪。

Kryo 對 Class 的序列化隻需要化 Class 的全路徑名,在反序列化時根據 Class 通過類加載進行加載,大大減少了序列化後的檔案大小,能極大提高性能。

Kryo 的核心設計理念就是盡最大可能減少序列化後的檔案大小,其舉措1就是通過對long,int等資料類型,采用變長位元組存儲來代替java中使用固定位元組(4,8)位元組的模式,因為在軟體開發中,對象的這些值基本上都是小值,能節省很多空間,第二個舉措是使用了類似緩存的機制,在一次序列化對象中,在整個遞歸序列化期間,相同的對象,隻會序列化一次,後續的用一個局部int值來代替。

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-24

本文作者:中間件興趣圈

本文來自:“

掘金

”,了解相關資訊可以關注“掘金”