溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java高性能序列化工具Kryo怎么使用

發(fā)布時(shí)間:2022-06-10 14:01:04 來源:億速云 閱讀:861 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“Java高性能序列化工具Kryo怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Java高性能序列化工具Kryo怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

    概述

    Kryo 是一個(gè)快速序列化/反序列化工具,依賴于字節(jié)碼生成機(jī)制(底層使用了 ASM 庫),因此在序列化速度上有一定的優(yōu)勢(shì),但正因如此,其使用也只能限制在基于 JVM 的語言上。

    和 Hessian 類似,Kryo 序列化出的結(jié)果,是其自定義的、獨(dú)有的一種格式。由于其序列化出的結(jié)果是二進(jìn)制的,也即 byte[],因此像 Redis 這樣可以存儲(chǔ)二進(jìn)制數(shù)據(jù)的存儲(chǔ)引擎是可以直接將 Kryo 序列化出來的數(shù)據(jù)存進(jìn)去。當(dāng)然你也可以選擇轉(zhuǎn)換成 String 的形式存儲(chǔ)在其他存儲(chǔ)引擎中(性能有損耗)。

    基礎(chǔ)用法

    介紹了這么多,接下來我們就來看看 Kryo 的基礎(chǔ)用法吧。其實(shí)對(duì)于序列化框架來說,API 基本都差不多,畢竟入?yún)⒑统鰠⑼ǔ6际谴_定的(需要序列化的對(duì)象/序列化的結(jié)果)。在使用 Kryo 之前,我們需要引入相應(yīng)的依賴。

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

    基本使用如下所示:

    import com.esotericsoftware.kryo.Kryo;
    import com.esotericsoftware.kryo.io.Input;
    import com.esotericsoftware.kryo.io.Output;
    import java.io.*;
    public class HelloKryo {
       static public void main(String[] args) throws Exception {
           Kryo kryo = new Kryo();
           kryo.register(SomeClass.class);
           SomeClass object = new SomeClass();
           object.value = "Hello Kryo!";
           Output output = new Output(new FileOutputStream("file.bin"));
           kryo.writeObject(output, object);
           output.close();
    
           Input input = new Input(new FileInputStream("file.bin"));
           SomeClass object2 = kryo.readObject(input, SomeClass.class);
           input.close();
           System.out.println(object2.value);
      }
       static public class SomeClass {
           String value;
      }
    }

    Kryo 類會(huì)自動(dòng)執(zhí)行序列化。Output 類和 Input 類負(fù)責(zé)處理緩沖字節(jié),并寫入到流中。如果序列化前和序列化后類的字段不一致,反序列化會(huì)失敗。

    Kryo 的序列化

    作為一個(gè)靈活的序列化框架,Kryo 并不關(guān)心讀寫的數(shù)據(jù),作為開發(fā)者,你可以隨意使用 Kryo 提供的那些開箱即用的序列化器。

    Kryo 的注冊(cè)

    和很多其他的序列化框架一樣,Kryo 為了提供性能和減小序列化結(jié)果體積,提供注冊(cè)的序列化對(duì)象類的方式。在注冊(cè)時(shí),會(huì)為該序列化類生成 int ID,后續(xù)在序列化時(shí)使用 int ID 唯一標(biāo)識(shí)該類型。

    注冊(cè)的方式如下:

    kryo.register(SomeClass.class);

    或者

    kryo.register(SomeClass.class, 1);

    可以明確指定注冊(cè)類的 int ID,但是該 ID 必須大于等于 0。如果不提供,內(nèi)部將會(huì)使用 int++的方式維護(hù)一個(gè)有序的 int ID 生成。

    Kryo 的序列化器

    Kryo 支持多種序列化器,通過源碼我們可窺知一二

    Java高性能序列化工具Kryo怎么使用

    雖然 Kryo 提供的序列化器可以讀寫大多數(shù)對(duì)象,但開發(fā)者也可以輕松的制定自己的序列化器。篇幅限制,這里就不展開說明了,僅以默認(rèn)的序列化器為例。

    對(duì)象引用

    在新版本的 Kryo 中,默認(rèn)情況下是不啟用對(duì)象引用的。這意味著如果一個(gè)對(duì)象多次出現(xiàn)在一個(gè)對(duì)象圖中,它將被多次寫入,并將被反序列化為多個(gè)不同的對(duì)象。

    舉個(gè)例子,當(dāng)開啟了引用屬性,每個(gè)對(duì)象第一次出現(xiàn)在對(duì)象圖中,會(huì)在記錄時(shí)寫入一個(gè) varint,用于標(biāo)記。當(dāng)此后有同一對(duì)象出現(xiàn)時(shí),只會(huì)記錄一個(gè) varint,以此達(dá)到節(jié)省空間的目標(biāo)。此舉雖然會(huì)節(jié)省序列化空間,但是是一種用時(shí)間換空間的做法,會(huì)影響序列化的性能,這是因?yàn)樵趯懭?讀取對(duì)象時(shí)都需要進(jìn)行追蹤。

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

    public class KryoReferenceDemo {
        public static void main(String[] args) throws FileNotFoundException {
            Kryo kryo = new Kryo();
            kryo.register(User.class);
            kryo.register(Account.class);
            User user = new User();
            user.setUsername("alvin");
            Account account = new Account();
            account.setAccountNo("10000");
            // 循環(huán)引用
            user.setAccount(account);
            account.setUser(user);
    
            // 這里需要設(shè)置為true,才不會(huì)報(bào)錯(cuò)
            kryo.setReferences(true);
    
            Output output = new Output(new FileOutputStream("kryoreference.bin"));
            kryo.writeObject(output, user);
            output.close();
    
            Input input = new Input(new FileInputStream("kryoreference.bin"));
            User object2 = kryo.readObject(input, User.class);
            input.close();
            System.out.println(object2.getUsername());
            System.out.println(object2.getAccount().getAccountNo());
        }
    
        public static class User {
            private String username;
            private Account account;
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public Account getAccount() {
                return account;
            }
            public void setAccount(Account account) {
                this.account = account;
            }
        }
        public static class Account {
            private String accountNo;
            private String amount;
            private User user;
            public String getAccountNo() {
                return accountNo;
            }
           public void setAccountNo(String accountNo) {
                this.accountNo = accountNo;
            }
            public String getAmount() {
                return amount;
            }
            public void setAmount(String amount) {
                this.amount = amount;
            }
            public User getUser() {
                return user;
            }
            public void setUser(User user) {
                this.user = user;
            }
        }
    }

    如果序列化前的setReferences(false), 后面設(shè)置setReferences(true)進(jìn)行反序列化,會(huì)失敗。

    線程不安全

    Kryo 不是線程安全的。每個(gè)線程都應(yīng)該有自己的 Kryo 對(duì)象、輸入和輸出實(shí)例。

    因此在多線程環(huán)境中,可以考慮使用 ThreadLocal 或者對(duì)象池來保證線程安全性。

    ThreadLocal + Kryo 解決線程不安全

    ThreadLocal 是一種典型的犧牲空間來換取并發(fā)安全的方式,它會(huì)為每個(gè)線程都單獨(dú)創(chuàng)建本線程專用的 kryo 對(duì)象。對(duì)于每條線程的每個(gè) kryo 對(duì)象來說,都是順序執(zhí)行的,因此天然避免了并發(fā)安全問題。創(chuàng)建方法如下:

    static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
      protected Kryo initialValue() {
         Kryo kryo = new Kryo();
         // 在此處配置kryo對(duì)象的使用示例,如循環(huán)引用等
         return kryo;
      };
    };
    
    Kryo kryo = kryos.get();

    之后,僅需要通過 kryos.get() 方法從線程上下文中取出對(duì)象即可使用。

    對(duì)象池 + Kryo 解決線程不安全

    「池」是一種非常重要的編程思想,連接池、線程池、對(duì)象池等都是

    「復(fù)用」思想的體現(xiàn),通過將創(chuàng)建的“對(duì)象”保存在某一個(gè)“容器”中,以便后續(xù)反復(fù)使用,避免創(chuàng)建、銷毀的產(chǎn)生的性能損耗,以此達(dá)到提升整體性能的作用。

    Kryo 對(duì)象池原理也是如此。Kryo 框架自帶了對(duì)象池的實(shí)現(xiàn),整個(gè)使用過程不外乎創(chuàng)建池、從池中獲取對(duì)象、歸還對(duì)象三步,以下為代碼實(shí)例。

    // Pool constructor arguments: thread safe, soft references, maximum capacity
    Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
      protected Kryo create () {
         Kryo kryo = new Kryo();
         // Kryo 配置
         return kryo;
      }
    };
    
    // 獲取池中的Kryo對(duì)象
    Kryo kryo = kryoPool.obtain();
    // 將kryo對(duì)象歸還到池中
    kryoPool.free(kryo);

    創(chuàng)建 Kryo 池時(shí)需要傳入三個(gè)參數(shù),其中第一個(gè)參數(shù)用于指定是否在 Pool 內(nèi)部使用同步,如果指定為 true,則允許被多個(gè)線程并發(fā)訪問。第三個(gè)參數(shù)適用于指定對(duì)象池的大小的,這兩個(gè)參數(shù)較容易理解,因此重點(diǎn)來說一下第二個(gè)參數(shù)。

    如果將第二個(gè)參數(shù)設(shè)置為 true,Kryo 池將會(huì)使用 java.lang.ref.SoftReference 來存儲(chǔ)對(duì)象。這允許池中的對(duì)象在 JVM 的內(nèi)存壓力大時(shí)被垃圾回收。Pool clean 會(huì)刪除所有對(duì)象已經(jīng)被垃圾回收的軟引用。當(dāng)沒有設(shè)置最大容量時(shí),這可以減少池的大小。當(dāng)池子有最大容量時(shí),沒有必要調(diào)用 clean,因?yàn)槿绻_(dá)到了最大容量,Pool free 會(huì)嘗試刪除一個(gè)空引用。

    創(chuàng)建完 Kryo 池后,使用 kryo 就變得異常簡(jiǎn)單了,只需調(diào)用 kryoPool.obtain() 方法即可,使用完畢后再調(diào)用 kryoPool.free(kryo) 歸還對(duì)象,就完成了一次完整的租賃使用。

    理論上,只要對(duì)象池大小評(píng)估得當(dāng),就能在占用極小內(nèi)存空間的情況下完美解決并發(fā)安全問題。如果想要封裝一個(gè) Kryo 的序列化方法,可以參考如下的代碼

    public static byte[] serialize(Object obj) {
       Kryo kryo = kryoPool.obtain();
       // 使用 Output 對(duì)象池會(huì)導(dǎo)致序列化重復(fù)的錯(cuò)誤(getBuffer返回了Output對(duì)象的buffer引用)
       try (Output opt = new Output(1024, -1)) {
           kryo.writeClassAndObject(opt, obj);
           opt.flush();
           return opt.getBuffer();
      }finally {
           kryoPool.free(kryo);
      }
    }

    讀到這里,這篇“Java高性能序列化工具Kryo怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI