Kryo對象序列化的來龍去脈

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

kryo對象序列化入口爲Kryo的writeClassAndObject。

Kryo#writeClassAndObject

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

代碼@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方法。

writeReferenceOrNull方法

 1/** @param object May be null if mayBeNull is true.
 2     * @return true if no bytes need to be written for the object. */
 3    boolean writeReferenceOrNull (Output output, Object object, boolean mayBeNull) {     // @1
 4        if (object == null) {                                                                                                    // @2                    
 5            if (TRACE || (DEBUG && depth == 1)) log("Write", null);
 6            output.writeVarInt(Kryo.NULL, true);
 7            return true;
 8        }
 9        if (!referenceResolver.useReferences(object.getClass())) {                                  // @3
10            if (mayBeNull) output.writeVarInt(Kryo.NOT_NULL, true);
11            return false;
12        }
13
14        // Determine if this object has already been seen in this object graph.
15        int id = referenceResolver.getWrittenId(object);                                                 // @4
16
17        // If not the first time encountered, only write reference ID.
18        if (id != -1) {                                                                                                        // @5
19            if (DEBUG) debug("kryo", "Write object reference " + id + ": " + string(object));
20            output.writeVarInt(id + 2, true); // + 2 because 0 and 1 are used for NULL and NOT_NULL.
21            return true;
22        }
23
24        // Otherwise write NOT_NULL and then the object bytes.
25        id = referenceResolver.addWrittenObject(object);     // @6
26        output.writeVarInt(NOT_NULL, true);
27        if (TRACE) trace("kryo", "Write initial object reference " + id + ": " + string(object));
28        return false;                                                              // @7
29    }

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

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

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

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

ListReferenceResolver#getWrittenId

1public int getWrittenId (Object object) {
2    for (int i = 0, n = seenObjects.size(); i < n; i++) {
3        if (seenObjects.get(i) == object) {
4            return i;
5        }
6    }
7    return -1;
8}

代碼@5:如果writtenId不等於-1,表示該對象已被序列化,直接序列化ID,直接返回true,然後結束writeClassAndObject該方法,表示該對象實例完成。

代碼@6:爲object構建一個ID,這個ID數據是在一次嵌套調用writeClassAndObject內有效,然後writeClassAndObject結束後,會調用reset方法,將其清空,然後先寫入爲空標識,並返回false,也就是第一次序列化對象時,返回false,會進入到writeClassAndObject的代碼@5中。 Kryo#writeClassAndObject 代碼@5

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

其實其重點關鍵,還是writeClassAndObject#writeClass也就是上文說的代碼@3,在序列化對象之前,首先先序列化該對象的類型,然後需要返回對應的字段序列器。例如,如果類的類型爲java.util.Map,則首先先要記錄類型爲Map,然後返回可以序列化Map的序列器,再例如類型如果是java.lang.String,則先序列化類型,然後序列化值,序列化值的序列器則爲StringSerializer,那如果是一個對象類型,例如cn.uce.demo.Student,自然第一步是先序列化類型cn.uce.demo.Student,接下來就需要序列化Student的各個字段的信息,返回的序列化爲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://cloud.tencent.com/developer/article/1443590

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章