1. 程式人生 > 其它 >【序列化和反序列化】Kryo

【序列化和反序列化】Kryo

  

一、Kryo介紹

Kryo是一個快速序列化/反序列化工具,依賴於位元組碼生成機制(底層使用了ASM庫),因此在序列化速度上有一定的優勢,但正因如此,其使用也只能限制在基於JVM的語言上。

Kryo序列化出的結果,是其自定義的,獨有的一種格式。由於其序列化出的結果是二進位制的,也即byte[],因此像redis這樣可以儲存二進位制資料的儲存引擎是可以直接將Kryo序列化出來的資料存進去。當然你也可以選擇轉換成String的形式儲存在其他儲存引擎中(效能有損耗)。

github地址:https://github.com/EsotericSoftware/kryo

 

二、基礎用法

引入依賴

<
dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>5.2.0</version> </dependency>

基本使用如下所示

package com.chenly.serialize.kryo;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; /** * * @author: chenly * @date: 2022-11-28 15:07 * @description: * @version: 1.0 */ public class KryoTest { static public void main(String[] args) { SomeClass object
= new SomeClass(); object.value = "Hello Kryo!"; Kryo kryo = new Kryo(); kryo.register(SomeClass.class); //序列化 Output output = new Output(new ByteArrayOutputStream()); kryo.writeObject(output, object); byte[] bytes = output.getBuffer(); output.close(); //反序列化 Input input = new Input(new ByteArrayInputStream(bytes)); SomeClass object2 = kryo.readObject(input, SomeClass.class); input.close(); System.out.println(object2.value); } static public class SomeClass { String value; } }

Kryo類會自動執行序列化,Output類和Input類負責處理緩衝位元組,並寫入到流中

 

三、Kryo的註冊

Kryo為了提供效能和減小序列化結果體積,提供註冊序列化物件類的方式。在註冊時,會為該序列化類生成int ID ,後續在序列化時使用int ID唯一標識該型別。註冊的方式如下

kryo.register(SomeClass.class);

或者

kryo.register(SomeClass.class,id); //可以明確指定註冊類的int ID,但是該ID必須大於等於0。如果不提供,內部將會使用int++的方式維護一個有序的int ID生成

四、Kyro物件引用

在新版本的Kryo中。預設情況下是不啟用物件引用的。這意味著如果一個物件多次出現在一個物件圖中,它將被多次寫入,並將被反序列化為多個不同的物件。

當開啟了引用屬性,每個物件第一次出現在物件圖中,會在記錄時寫入一個varint,用於標記。當此後有同一物件出現時,只會記錄一個varint,以此達到節省空間的目標,此舉雖然會節省序列化空間,但是是一種用時間換空間的做法,會影響序列化效能,這是因為在寫入/讀取物件時都需要進行追蹤。

開發者可以使用kryo自帶的setReferences方法來決定是否啟用kryo的引用功能。

 

五、Kyro執行緒不安全

kryo不是執行緒安全的。每個執行緒都應該有自己的Kryo物件,輸入和輸出例項。

因此在多執行緒環境中。可以考慮使用ThreadLocal或者物件池來保證執行緒安全性。

ThreadLocal + Kryo解決執行緒不安全

ThreadLocal是一種典型的犧牲空間來換取併發安全的方式,它會為每個執行緒都單獨建立本執行緒專用的kryo物件。對於每個執行緒的每個kryo物件來說,都是順序執行的,因此天然避免了併發安全問題,建立方法如下:

private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>(){
    @Override
    protected Kryo initialValue() {
        Kryo kryo = new Kryo();
        return kryo;
    };
};

之後,僅需要通過kryoThreadLocal.get()方法從執行緒上下文中取出物件即可使用

Kryo kryo = kryoThreadLocal.get();

ThreadLocal + Kryo解決執行緒不安全

「池」是一種非常重要的程式設計思想,連線池、執行緒池、物件池等都是「複用」思想的體現,通過將建立的“物件”儲存在某一個“容器”中,以便後續反覆使用,避免建立、銷燬的產生的效能損耗,以此達到提升整體效能的作用。

Kryo 物件池原理也是如此。Kryo 框架自帶了物件池的實現,整個使用過程不外乎建立池、從池中獲取物件、歸還物件三步,以下為程式碼例項。

Pool<Kryo> kryoPool = new Pool<Kryo>(true,false,8) {
    @Override
    protected Kryo create() {
        Kryo kryo = new Kryo();
     ... //kryo配置
        return kryo;
    }
};

建立Kryo池時需要傳入三個引數,其中第一個引數用於指定是否在pool內部使用同步,如果指定為true,則允許被多個執行緒併發訪問。第三個引數是用於指定物件池的大小的。

第二個引數如果設定為true,Kryo池將會使用 java.lang.ref.SoftReference 來儲存物件。這允許池中的物件在JVM的記憶體壓力大時被垃圾回收。Pool clean會刪除所有物件已經被垃圾回收的軟引用。當沒有設定最大容量時,這可以減少池的大小。當池子有最大容量時,沒有必要將此引數設定為true ,因為如果達到了最大容量,Pool free 會嘗試刪除一個空引用

建立完 Kryo 池後,使用 kryo 就變得異常簡單了,只需呼叫 kryoPool.obtain() 法即可,使用完畢後再呼叫 kryoPool.free(kryo) 歸還物件,就完成了一次完整的租賃使用。

//獲取池中的Kryo物件
Kryo kryo =  kryoPool.obtain();
//將Kryo物件歸還到池中
kryoPool.free(kryo);

 理論上,只要物件池大小評估得當,就能在佔用極小記憶體空間的情況下完美解決併發安全問題

 

六、小結

相較於 JDK 自帶的序列化方式,Kryo 的效能更快,並且由於 Kryo 允許多引用和迴圈引用,在儲存開銷上也更小