您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Java高性能序列化工具Kryo怎么使用”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java高性能序列化工具Kryo怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
Kryo 是一個快速序列化/反序列化工具,依賴于字節碼生成機制(底層使用了 ASM 庫),因此在序列化速度上有一定的優勢,但正因如此,其使用也只能限制在基于 JVM 的語言上。
和 Hessian 類似,Kryo 序列化出的結果,是其自定義的、獨有的一種格式。由于其序列化出的結果是二進制的,也即 byte[],因此像 Redis 這樣可以存儲二進制數據的存儲引擎是可以直接將 Kryo 序列化出來的數據存進去。當然你也可以選擇轉換成 String 的形式存儲在其他存儲引擎中(性能有損耗)。
介紹了這么多,接下來我們就來看看 Kryo 的基礎用法吧。其實對于序列化框架來說,API 基本都差不多,畢竟入參和出參通常都是確定的(需要序列化的對象/序列化的結果)。在使用 Kryo 之前,我們需要引入相應的依賴。
<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 類會自動執行序列化。Output 類和 Input 類負責處理緩沖字節,并寫入到流中。如果序列化前和序列化后類的字段不一致,反序列化會失敗。
作為一個靈活的序列化框架,Kryo 并不關心讀寫的數據,作為開發者,你可以隨意使用 Kryo 提供的那些開箱即用的序列化器。
和很多其他的序列化框架一樣,Kryo 為了提供性能和減小序列化結果體積,提供注冊的序列化對象類的方式。在注冊時,會為該序列化類生成 int ID,后續在序列化時使用 int ID 唯一標識該類型。
注冊的方式如下:
kryo.register(SomeClass.class);
或者
kryo.register(SomeClass.class, 1);
可以明確指定注冊類的 int ID,但是該 ID 必須大于等于 0。如果不提供,內部將會使用 int++的方式維護一個有序的 int ID 生成。
Kryo 支持多種序列化器,通過源碼我們可窺知一二
雖然 Kryo 提供的序列化器可以讀寫大多數對象,但開發者也可以輕松的制定自己的序列化器。篇幅限制,這里就不展開說明了,僅以默認的序列化器為例。
在新版本的 Kryo 中,默認情況下是不啟用對象引用的。這意味著如果一個對象多次出現在一個對象圖中,它將被多次寫入,并將被反序列化為多個不同的對象。
舉個例子,當開啟了引用屬性,每個對象第一次出現在對象圖中,會在記錄時寫入一個 varint,用于標記。當此后有同一對象出現時,只會記錄一個 varint,以此達到節省空間的目標。此舉雖然會節省序列化空間,但是是一種用時間換空間的做法,會影響序列化的性能,這是因為在寫入/讀取對象時都需要進行追蹤。
開發者可以使用 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"); // 循環引用 user.setAccount(account); account.setUser(user); // 這里需要設置為true,才不會報錯 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), 后面設置setReferences(true)進行反序列化,會失敗。
Kryo 不是線程安全的。每個線程都應該有自己的 Kryo 對象、輸入和輸出實例。
因此在多線程環境中,可以考慮使用 ThreadLocal 或者對象池來保證線程安全性。
ThreadLocal 是一種典型的犧牲空間來換取并發安全的方式,它會為每個線程都單獨創建本線程專用的 kryo 對象。對于每條線程的每個 kryo 對象來說,都是順序執行的,因此天然避免了并發安全問題。創建方法如下:
static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); // 在此處配置kryo對象的使用示例,如循環引用等 return kryo; }; }; Kryo kryo = kryos.get();
之后,僅需要通過 kryos.get() 方法從線程上下文中取出對象即可使用。
「池」是一種非常重要的編程思想,連接池、線程池、對象池等都是
「復用」思想的體現,通過將創建的“對象”保存在某一個“容器”中,以便后續反復使用,避免創建、銷毀的產生的性能損耗,以此達到提升整體性能的作用。
Kryo 對象池原理也是如此。Kryo 框架自帶了對象池的實現,整個使用過程不外乎創建池、從池中獲取對象、歸還對象三步,以下為代碼實例。
// 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對象 Kryo kryo = kryoPool.obtain(); // 將kryo對象歸還到池中 kryoPool.free(kryo);
創建 Kryo 池時需要傳入三個參數,其中第一個參數用于指定是否在 Pool 內部使用同步,如果指定為 true,則允許被多個線程并發訪問。第三個參數適用于指定對象池的大小的,這兩個參數較容易理解,因此重點來說一下第二個參數。
如果將第二個參數設置為 true,Kryo 池將會使用 java.lang.ref.SoftReference 來存儲對象。這允許池中的對象在 JVM 的內存壓力大時被垃圾回收。Pool clean 會刪除所有對象已經被垃圾回收的軟引用。當沒有設置最大容量時,這可以減少池的大小。當池子有最大容量時,沒有必要調用 clean,因為如果達到了最大容量,Pool free 會嘗試刪除一個空引用。
創建完 Kryo 池后,使用 kryo 就變得異常簡單了,只需調用 kryoPool.obtain() 方法即可,使用完畢后再調用 kryoPool.free(kryo) 歸還對象,就完成了一次完整的租賃使用。
理論上,只要對象池大小評估得當,就能在占用極小內存空間的情況下完美解決并發安全問題。如果想要封裝一個 Kryo 的序列化方法,可以參考如下的代碼
public static byte[] serialize(Object obj) { Kryo kryo = kryoPool.obtain(); // 使用 Output 對象池會導致序列化重復的錯誤(getBuffer返回了Output對象的buffer引用) try (Output opt = new Output(1024, -1)) { kryo.writeClassAndObject(opt, obj); opt.flush(); return opt.getBuffer(); }finally { kryoPool.free(kryo); } }
讀到這里,這篇“Java高性能序列化工具Kryo怎么使用”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。