Kryo做爲一個優秀的Java序列化方案,在網上能找到很多測評,但未見系統的中文入門或說明文檔。官方文檔是最好的學習文檔。雖然英文不差,但啃下來畢竟沒母語來的舒服。這裏抽出時間作些翻譯,以方便你們查閱。爲閱讀流暢,文中選擇性的未翻譯某些專業詞彙,如 buffer、scheme等, 並在雙尖括號中給出了譯者註釋,形如 << 註釋... >> 。如遇到邏輯錯誤、閱讀不通等,請參閱原文文檔,並感謝您的指正。java
翻譯的源官方文檔更新於2017年5月,本文初次翻譯於2017年5月底。git
官方文檔地址:github
https://github.com/EsotericSoftware/kryo/blob/master/README.mdobjective-c
=============================如下爲翻譯正文=============================數據庫
Kryo是一種快速高效的Java對象圖(Object graph)序列化框架。 該項目的目標是速度、效率和易於使用的API。 當對象須要持久化時,不管是用於文件、數據庫仍是經過網絡,該項目都頗有用。<<譯者注:Object graph >>apache
Kryo還能夠執行自動深層淺層的複製/克隆。這是從對象直接複製到對象,而不是object-> bytes-> object。api
本文檔適用於 Kryo v2.0 版本。 請參閱v1.x的 V1Documentation 。數組
若是您計劃使用 Kryo 進行網絡通訊, KryoNet 項目對您可能會有幫助。緩存
版本4.0.0爲穩定性和性能帶來了幾項新功能和改進。如下是您應留意的一些亮點(此外,您還應仔細的研究變動日誌並測試升級)。安全
重要提示:此改變會破壞 FieldSerializer 對於通用字段的序列化格式,所以使用 Kryo 3 和 FieldSerializer 序列化的通用類默認不能使用 Kryo 4 進行反序列化。爲了反序列化 Kryo 3 序列化過的普通類數據,你必須設置 kryo.getFieldSerializerConfig().setOptimizedGenerics(true);! 您須要在各類案例中,測試序列化/反序列化的升級工做。
詳細信息請參閱發佈說明。
對於序列化庫,最重要的評判標準是它能夠反序列化之前的序列化數據。對於kryo,咱們也按照這個首要法則進行版本控制:
重定義規則 1.): 若是任何底層二進制格式被更改(參見 IO 和 Unsafe-based IO)或者經常使用序列化器生成的數據被改變了(例如一些默認的序列化器)。
重定義規則 2.): 因爲java訪問修飾符的限制,技術 api 比語義 api ( documented on this page ) 的做用域更廣了。所以,在沒有用戶受到影響的狀況下,技術上的二進制兼容性可能會被破壞。
<<譯者注: 重定義規則 即 給出了規則1 和 規則2 的另外一種表述>>
請牢記,序列化庫的任何一次升級都是重大事件。升級 kryo 時,請檢查升級帶來的全部改變,並用您的數據結構和環境設置進行完全的測試。
咱們儘可能使升級過程簡易並安全:
Kryo JAR 可在發佈頁面和 Maven Central 上找到。 Kryo 的最新快照,包括 master 的快照構建,都在 Sonatype Repository 中。
要使用Kryo的官方版本,請在 pom.xml 中使用如下代碼段
<dependency>
<groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.0</version> </dependency>
若是您由於在類路徑中使用了不一樣版本的 asm 而遇到問題,可使用包含這個 asm 版本的 kryo-shaded jar,並將其從新放置在不一樣的包中:
<dependency>
<groupId>com.esotericsoftware</groupId> <artifactId>kryo-shaded</artifactId> <version>4.0.0</version> </dependency>
若是要測試 Kryo 的最新快照,請在 pom.xml 中使用如下代碼片斷
<repository>
<id>sonatype-snapshots</id> <name>sonatype snapshots repo</name> <url>https://oss.sonatype.org/content/repositories/snapshots</url> </repository> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.1-SNAPSHOT</version> </dependency>
若是您使用沒有 Maven 的 Kryo,請注意 Kryo jar 文件有幾個外部依賴關係,它們的JAR也須要添加到類路徑中。這些依賴關係是 MinLog logging library 和 Objenesis library。
向上翻頁以查看類庫的使用:
Kryo kryo = new Kryo(); // ... Output output = new Output(new FileOutputStream("file.bin")); SomeClass someObject = ... kryo.writeObject(output, someObject); output.close(); // ... Input input = new Input(new FileInputStream("file.bin")); SomeClass someObject = kryo.readObject(input, SomeClass.class); input.close();
Kryo 類執行序列化。 輸出和輸入類處理緩衝字節,並可選地刷新到流(stream)。
本文檔的其他部分詳細介紹了它的工做原理和庫的高級用法。
Output 類是將數據寫入字節數組 buffer 的 OutputStream。若是須要字節數組,則能夠直接獲取和使用該 buffer 。若是已經給定了OutputStream,當 buffer 滿時,它將刷新 bytes 到 stream。Output有許多方法能夠有效地將基本類型和字符串寫爲 bytes 。它提供相似於DataOutputStream,BufferedOutputStream,FilterOutputStream 和 ByteArrayOutputStream 類的功能。
在寫入 OutputStream 時 buffer 後,請確保調用 flush() 或 close(),以便將緩衝的字節寫入底層流。
Input 類是從字節數組緩衝區讀取數據的 InputStream 類。若是須要從字節數組中讀取,能夠直接設置該緩衝區 (buffer) 。若是給定了InputStream,當緩衝區用盡時,它將從流中填充緩衝區。Input 有許多方法能夠高效地從 bytes 讀取基本類型和字符串。它提供相似於DataInputStream,BufferedInputStream,FilterInputStream 和 ByteArrayInputStream 類的功能。
若是要讀寫字節數組 (bytes)之外的對象,只需提供相應的 InputStream 或 OutputStream 便可。
Kryo 提供了額外的基於 sun.misc.Unsafe 的 UnsafeInput,UnsafeOutput IO 類。由於他們來源於 Kryo 的 Input 和 Output 類,因此能夠在支持sun.misc.Unsafe 的平臺上進行替換。
當須要從直接內存 ByteBuffers 或非堆內存中進行序列化或反序列化,可用爲其定製的專用類 UnsafeMemoryInput 和 UnsafeMemoryOutput,而沒必要使用一般的 Input 和 Output 類。
基於應用程序的具體狀況,使用 Unsafe-based IO 會顯着地性能提高(有時可達一個數量級)。特別地,將大型原始數組做爲對象的一部分進行序列化時,這種方式會有很大的幫助。
當涉及序列化數據的二進制格式時,不保證 Unsafe-based IO 與 Kryo 的輸入和輸出流徹底兼容。
這意味着 Unsafe-based IO 的輸出流只能被 Unsafe-based IO 的輸入流讀取,而不能由一般的輸入流讀取。反之亦然,普通的輸出流產生的數據不能被 Unsafe-based IO 的輸入流正確讀取。
只要序列化和反序列化都採用 Unsafe IO 流,而且都在相同的處理器架構(更準確地說是,整數和浮點類型的字節順序和內部表示相同)上執行,就能保證(should be)數據安全。
在 X86 架構上對 Unsafe IO 進行過全面的測試。其餘的處理器架構上沒有以相同的程度進行測試。例如,咱們有收到過 SPARC 平臺上的一些錯誤報告。
public class ColorSerializer extends Serializer<Color> { public void write (Kryo kryo, Output output, Color object) { output.writeInt(object.getRGB()); } public Color read (Kryo kryo, Input input, Class<Color> type) { return new Color(input.readInt(), true); } }
Serializer 有兩種能夠實現的方法。 write() 將該對象寫入字節 (bytes)。 read() 建立對象的新實例,並用輸入數據填充對象。
Kryo 實例可用於寫入和讀取嵌套對象。若是 Kryo 用於讀取 read() 中的嵌套對象,且若是嵌套對象能夠引用父對象,則必須先調用 kryo.reference() 引用父對象。若是嵌套對象不能引用父對象,或 Kryo 不被用於嵌套對象,或者沒有使用引用,則沒有必要調用 kryo.reference() 。若是嵌套對象可使用相同的serializer,那麼 serializer 必須是可重入的。
代碼不該該直接使用 serializers,而應該使用 Kryo 的讀寫方法。這將使 Kryo 來協調序列化並處理諸如引用和空對象的特徵。
默認狀況下,serializers 不須要處理爲空的對象。 Kryo 框架將根據須要寫一個字節,以表示 null 或非 null。若是一個 serializers 想要更高效地本身來處理 nulls,能夠設置 Serializer#setAcceptsNull(true)。這個設置也能夠在已知類型的全部實例永不爲空時,來避免寫入空標識字節。
當Kryo寫出一個對象的實例時,首先可能須要寫出一些標識對象類的東西。默認狀況下,寫入完整類名,而後寫入該對象的字節。後續出現的同一類對象圖的對象用變長的int來寫(using a variable length int)。寫類的名字有點低效,因此類能夠事先註冊:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class); // ... Output output = ... SomeClass someObject = ... kryo.writeObject(output, someObject);
這裏,SomeClass 註冊到了 Kryo,它將該類與一個 int 型的 ID 相關聯。當 Kryo 寫出 SomeClass 的一個實例時,它會寫出這個 int ID。這比寫出類名更有效。在反序列化期間,註冊的類必須具備序列化期間相同的 ID 。上面展現的註冊方法分配下一個可用的最小整數 ID,這意味着類被註冊的順序十分重要。註冊時也能夠明確指定特定 ID,這樣的話註冊順序就不重要了:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class, 10); kryo.register(AnotherClass.class, 11); kryo.register(YetAnotherClass.class, 12);
當 IDs 是小的正整數時最有效。負數不能有效地序列化。 -1 和 -2 是保留值。<<譯者注:-1 和-2 有其餘含義>>
能夠混合使用註冊和未註冊的類。默認使用 ID 0-9 註冊全部基本類型,基本類包裝器,String 和 void。因此要當心此範圍內的註冊覆蓋的狀況。
當 Kryo#setRegistrationRequired 設置爲true,可在遇到任何未註冊的類時拋出異常。這能阻止應用程序使用類名字符串來序列化。
若是使用未註冊的類,則應考慮使用較短的包名。
寫入類標識符後,Kryo使用一個序列化器(serializer) 來寫入對象的字節。當類被註冊後,serializer 實例就能被肯定了:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class, new SomeSerializer()); kryo.register(AnotherClass.class, new AnotherSerializer());
若是一個類未註冊或沒有指定序列化器,則會自動從映射了類和序列化器的「 默認序列化器 (default serializers)」列表中選擇一個序列化器。如下類默認設置過序列化器:
能夠另外添加其餘的默認序列化器:
Kryo kryo = new Kryo(); kryo.addDefaultSerializer(SomeClass.class, SomeSerializer.class); // ... Output output = ... SomeClass someObject = ... kryo.writeObject(output, someObject);
也可使用 DefaultSerializer 註解:
@DefaultSerializer(SomeClassSerializer.class) public class SomeClass { // ... }
若是類沒有匹配默認序列化器,那麼默認狀況下會使用 FieldSerializer 。這也能改變:
Kryo kryo = new Kryo(); kryo.setDefaultSerializer(AnotherGenericSerializer.class);
一些序列化器容許提供額外的信息,以減小輸出字節數:
Kryo kryo = new Kryo(); FieldSerializer someClassSerializer = new FieldSerializer(kryo, SomeClass.class); CollectionSerializer listSerializer = new CollectionSerializer(); listSerializer.setElementClass(String.class, kryo.getSerializer(String.class)); listSerializer.setElementsCanBeNull(false); someClassSerializer.getField("list").setClass(LinkedList.class, listSerializer); kryo.register(SomeClass.class, someClassSerializer); // ... SomeClass someObject = ... someObject.list = new LinkedList(); someObject.list.add("thishitis"); someObject.list.add("bananas"); kryo.writeObject(output, someObject);
在這個例子中,FieldSerializer 用於 SomeClass 的序列化過程。由於配置了 FieldSerializer,因此「 list 」字段將始終爲 LinkedList,並會使用指定的 CollectionSerializer。 由於配置了 CollectionSerializer,所以每一個元素將是一個String,且都不爲 null。這會使序列化過程更有效率。這種狀況下,列表中每一個元素節省了2到3個字節。
默認狀況下,大多數類最終會使用 FieldSerializer。 它本質上是自動化的處理了手動的序列化過程。 FieldSerializer 會被直接分配給對象的字段。 若是這些字段是 public,protected,或默認的訪問權限(package private),且不是final的,將採用字節碼生成技術(bytecode generation)以求速度(參見 ReflectASM)。對於私有字段,使用 setAccessible 和緩存反射技術,速度也不慢。
也提供了其餘通用的序列化器,如 BeanSerializer,TaggedFieldSerializer,CompatibleFieldSerializer 和 VersionFieldSerializer。更多的序列化器可在 github 和 kryo-serializer 上找到。
雖然 FieldSerializer 是大多數類的理想選擇,但類也能夠方便地進行本身的序列化。 這須要實現 KryoSerializable 接口(相似於 JDK 中的java.io.Externalizable 接口)。
public class SomeClass implements KryoSerializable { // ... public void write (Kryo kryo, Output output) { // ... } public void read (Kryo kryo, Input input) { // ... } }
雖然很是罕見,但有一些類不能被 Kryo 序列化。 在這種狀況下,可使用 Kryo 的 JavaSerializer 提供後備解決方案,並使用標準Java序列化。只要對象能被Java序列化,就能採用這種方案。雖然這種方案與一般的 Java 序列化同樣低效,但至少可以完成序列化工做。 固然,在這種方案中,正如一般的 Java 序列化所要求的那樣,你的類須要實現 Serializable 或 Externalizable 接口。
當您的類實現了 Java 的 Serializable 接口,那麼您就能使用 Kryo 專用的 JavaSerializer 序列化器:
kryo.register(SomeClass.class, new JavaSerializer());
若是您的類實現了Java的 Externalizable 接口,那麼您就能使用 Kryo 專用的 ExternalizableSerializer 序列化器:
kryo.register(SomeClass.class, new ExternalizableSerializer());
典型地,當使用 FieldSerializer 時,它可以自動檢測類的每一個字段應該使用哪一個序列化器。但在某些狀況下,您可能會但願更改默認規則並自定義某個字段的序列化。
爲此,Kryo 提供了一組註解。 @Bind 可用於任何字段,@CollectionBind 用於類型爲集合(collection)的字段,@MapBind 用於類型爲 map 的字段:
public class SomeClass { // Use a StringSerializer for this field @Bind(StringSerializer.class) Object stringField; // Use a MapSerializer for this field. Keys should be serialized // using a StringSerializer, whereas values should be serialized // using IntArraySerializer @BindMap( valueSerializer = IntArraySerializer.class, keySerializer = StringSerializer.class, valueClass = int[].class, keyClass = String.class, keysCanBeNull = false) Map map; // Use a CollectionSerializer for this field. Elements should be serialized // using LongArraySerializer @BindCollection( elementSerializer = LongArraySerializer.class, elementClass = long[].class, elementsCanBeNull = false) Collection collection; // ... }
Kryo有三組讀寫對象的方法。
若是不知道對象的具體類,且對象能夠爲null:
kryo.writeClassAndObject(output, object);
// ... Object object = kryo.readClassAndObject(input); if (object instanceof SomeClass) { // ... }
若是類已知且對象能夠爲null:
kryo.writeObjectOrNull(output, someObject);
// ... SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);
若是類已知且對象不能爲null:
kryo.writeObject(output, someObject);
// ... SomeClass someObject = kryo.readObject(input, SomeClass.class);
默認狀況下,圖中每一個對象從第二個開始的表象都以整數順序存儲。這種方式能夠序列化相同對象和循環圖的多個引用。它具備少許的開銷,若是不須要,能夠禁用以節省空間:
Kryo kryo = new Kryo(); kryo.setReferences(false); // ...
當使用 Kryo 做爲嵌套對象的序列化器時,必須在 read() 中調用 kryo.reference()。有關詳細信息,請參閱 Serializers。
特定類型的序列化器使用 Java 代碼建立該類型的新實例。序列化器如 FieldSerializer 是泛型的,用於處理建立任何類的新實例。默認狀況下,若是某個類有一個無參構造方法,那麼它將經過 ReflectASM 或反射來調用,不然拋出異常。若是無參構造方法是私有的,則嘗試經過setAccessible用反射來訪問它。這樣的過程,可使 Kryo 在不影響公共API的狀況下建立類的實例。
當不能使用 ReflectASM 或反射時,能夠配置 Kryo 使用 InstantiatorStrategy 來建立類的實例。Objenesis 提供 StdInstantiatorStrategy,它使用JVM 特定的 API 來建立類的實例,而不會調用任何構造方法。雖然這適用於許多 JVM,但無參構造方法的移植性更好。
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
請注意,須要設計類來按下述的方式建立。若是指望調用一個類的構造函數,那麼當經過這種機制建立時,它可能處於未初始化的狀態。
在許多狀況下,您可能但願有這樣的策略:Kryo 首先嚐試使用無參構造方法,若是嘗試失敗,再嘗試使用 StdInstantiatorStrategy 做爲後備方案,由於後備方案不須要調用任何構造方法。這種策略的配置能夠這樣表示:
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
然而,這樣的默認行爲須要一個無參構造方法。
Objenesis 還可使用Java的內置序列化機制建立新對象。如此的話,類必須實現 java.io.Serializable,且在調用時會執行父類中的第一個無參構造方法。
kryo.setInstantiatorStrategy(new SerializingInstantiatorStrategy());
您也能夠編寫本身的 InstantiatorStrategy。
要定製特定類型的建立方式,能夠設置一個 ObjectInstantiator。這會覆蓋 ReflectASM,反射和 InstantiatorStrategy。
Registration registration = kryo.register(SomeClass.class); registration.setObjectInstantiator(...);
另外,一些序列化器提供重寫的方式定製對象的建立。
kryo.register(SomeClass.class, new FieldSerializer(kryo, SomeClass.class) { public Object create (Kryo kryo, Input input, Class type) { return new SomeClass("some constructor arguments", 1234); } });
序列化庫須要關於如何建立新實例、獲取和設置值、導航對象圖等的特定信息。這些幾乎是複製對象所需的一些,所以 Kryo 支持自動生成深和淺的對象副本。注意 Kryo 的複製不會序列化爲字節而後反轉,它使用直接分配。
Kryo kryo = new Kryo(); SomeClass someObject = ... SomeClass copy1 = kryo.copy(someObject); SomeClass copy2 = kryo.copyShallow(someObject);
Serializer 類有一個複製方法能夠完成複製工做。若是實現了特定於應用程序的序列化器而不使用複製功能時,能夠忽略這些方法。 Kryo 提供的全部序列化器都支持複製。多個對同一個對象的引用和循環引用由框架自動處理。
與 read() Serializer 方法相似,必須先調用 kryo.reference(),而後才能使用 Kryo 來複制子對象。有關詳細信息,請參閱序列化器。
相似於 KryoSerializable,類能夠實現 KryoCopyable 進行本身的複製:
public class SomeClass implements KryoCopyable<SomeClass> { // ... public SomeClass copy (Kryo kryo) { // Create new instance and copy values from this instance. } }
Kryo 有兩種上下文方法。 getContext() 返回一個用於存儲用戶數據的 map。 因爲 Kryo 實例可用於全部序列化器,所以此數據能夠隨時得到。 getGraphContext() 與之相似,但在每一個對象圖被序列化或反序列化以後被清除。 這樣能夠方便地管理每一個對象圖狀態。
Kryo 支持流,所以在全部序列化字節上使用壓縮或加密是不太必要的:
OutputStream outputStream = new DeflaterOutputStream(new FileOutputStream("file.bin")); Output output = new Output(outputStream); Kryo kryo = new Kryo(); kryo.writeObject(output, object); output.close();
若有須要,可使用序列化器來壓縮或加密對象圖字節中的一個部分字節。 例如,請參閱 DeflateSerializer 或 BlowfishSerializer。 這些序列化器包裝了另外一個序列化器,並對字節進行編碼和解碼。
有時先寫一些數據的長度,而後再寫入數據的機制是頗有用的。若是數據長度不能提早知道,則須要緩衝全部數據以肯定其長度,而後寫入長度,再而後寫入數據。這種緩衝能防止流式傳輸而且潛在地須要很是大的緩衝區(buffer),這並不理想。
分塊編碼經過使用小的緩衝區(buffer)來解決這個問題。當緩衝區已滿時,其長度被寫入,而後是數據。這是一個數據塊的機制。當多個塊時,緩衝區被清除,這樣繼續直到沒有更多的數據寫入。長度爲零的塊表示塊的結尾。
Kryo 提供了簡單的分塊編碼的類。 OutputChunked 用於寫分塊數據。它擴展了 Output,因此有方便的方法來寫入數據。當 OutputChunked 緩衝區已滿時,它將該塊刷新到包裝的 OutputStream。 endChunks() 方法用於標記一組塊的結尾。
OutputStream outputStream = new FileOutputStream("file.bin"); OutputChunked output = new OutputChunked(outputStream, 1024); // Write data to output... output.endChunks(); // Write more data to output... output.endChunks(); // Write even more data to output... output.close();
如要讀取分塊數據,使用 InputChunked。它繼承了 Input,因此有方法來讀取數據。當讀取時,InputChunked 將在到達一組塊的末尾時做爲數據的末尾。nextChunks() 方法前進到下一組塊,即便當前塊組中的數據並未讀取完。
InputStream outputStream = new FileInputStream("file.bin"); InputChunked input = new InputChunked(inputStream, 1024); // Read data from first set of chunks... input.nextChunks(); // Read data from second set of chunks... input.nextChunks(); // Read data from third set of chunks... input.close();
對於某些需求,特別是長期存儲序列化後的bytes,序列化如何處理類的變化相當重要。這被稱爲 forward(讀取較新類的序列化生成的字節)和 backword(讀取由舊類序列化產生的字節)兼容性。
FieldSerializer 是最經常使用的 serializer。它是通用的,能夠序列化大多數類而無需任何配置。它是高效的,只寫字段數據,沒有任何額外信息。它不支持添加,刪除或更改字段類型,不會使先前的序列化字節無效。大多狀況下,這能夠接受,例如經過網絡發送數據,可是做爲長期數據存儲,這不是個好方法,由於Java 類不能演化。因爲 FieldSerializer 默認嘗試讀寫非 public 字段,所以評估將被序列化的每一個類是很重要的工做。
當沒有指定序列化程序時,默認狀況下使用 FieldSerializer。若有必要,可使用另外一種通用的序列化器:
kryo.setDefaultSerializer(TaggedFieldSerializer.class);
BeanSerializer 很是相似於 FieldSerializer,除了它使用 bean getter 和 setter 方法,而不是直接的字段訪問。速度上來講,這稍慢些,但由於它使用公共 API 來配置對象,可能會更安全。
VersionFieldSerializer 擴展了 FieldSerializer,並容許字段具備 @Since(int) 註解來指示它們被添加的版本。對於特定字段,@Since 中的值不該該在建立後改變。這不如 FieldSerializer 那麼靈活,後者能夠處理大多數類而不須要註解,但前者提供向後兼容性。這意味着能夠添加新的字段,但刪除,重命名或更改任何字段的類型將使先前的序列化字節失效。與 FieldSerializer 相比,VersionFieldSerializer 具備很是少的開銷(一個額外的變量)。
TaggedFieldSerializer 將 FieldSerializer 擴展爲僅序列化具備 @Tag(int) 註解的字段,提供向後兼容性,從而能夠添加新字段。而且它還經過setIgnoreUnknownTags(true) 提供向前兼容性,所以任何未知的字段 tags 將被忽略。 對比 VersionFieldSerializer,TaggedFieldSerializer 有兩個優勢:1)字段能夠被重命名,2)標記有 @Deprecated 註解的字段,在讀取舊字節時或寫出新字節時將被忽略。儘管字段和 @Tag 註解必須保留在類中,棄用機制有效地從序列化中刪除了廢棄的字段。廢棄的字段能被設置私有和/或重命名,這樣他們不會弄亂類的信息(例如,ignored, ignored2)。基於這些緣由,TaggedFieldSerializer 爲類的演化提供更多的靈活性。缺點是與 VersionFieldSerializer(每一個字段需額外的一個變量)相比,它具備少許額外的開銷。
CompatibleFieldSerializer 擴展了 FieldSerializer 以提供向前和向後兼容性,這意味着能夠添加或刪除字段,而不會使先前的序列化字節無效。它不支持更改字段的類型。像 FieldSerializer 同樣,它能夠序列化大多數類而不須要註解。前向和後向兼容性有一些代價:在序列化中第一次遇到某個類時,會寫入一個包含字段名稱字符串的簡單 scheme。同時,在序列化和反序列化期間,緩衝區用以執行分塊編碼。這種機制使得CompatibleFieldSerializer 可以忽略它不認識的字段的字節。當 Kryo 配置爲使用引用時,若是某個字段被刪除,可能引起CompatibleFieldSerializer 的問題。若是您的類繼承層次結構包含相同的命名字段,請使用 CachedFieldNameStrategy.EXTENDED 策略。
class A { String a; } class B extends A { String a; } ... // use `EXTENDED` name strategy, otherwise serialized object can't be deserialized correctly. Attention, `EXTENDED` strategy increases the serialized footprint. kryo.getFieldSerializerConfig().setCachedFieldNameStrategy(FieldSerializer.CachedFieldNameStrategy.EXTENDED);
能夠輕鬆開發額外的序列化器,用於向前和向後兼容性,例如使用外部手寫 schema 的序列化器。
提供的 Kryo 序列化器默認假設將用 Java 反序列化,所以它們不會明肯定義寫入的格式。序列化器可使用更容易被其餘語言讀取的標準格式寫入,但默認狀況下不提供。
序列化器 Kryo 在序列化嵌套對象時使用調用堆棧。 Kryo 使用最小的堆棧調用,但對於極深的對象圖,可能會發生堆棧溢出。這是大多數序列化庫的常見問題,包括內置的 Java 序列化。可使用 -Xss 增長堆棧大小,但請注意,這項配置做用於全部線程。JVM 中具備多個線程且巨大的堆棧大小會致使佔用大量內存。
Kryo 不是線程安全的。每一個線程都應該有本身的 Kryo,Input 和 Output 實例。此外, bytes[] Input 可能被修改,而後在反序列化期間回到初始狀態,所以不該該在多線程中併發使用相同的 bytes[]。
由於 Kryo 實例的建立/初始化是至關昂貴的,因此在多線程的狀況下,您應該池化 Kryo 實例。一個很是簡單的解決方案是使用 ThreadLocal 將 Kryo實例綁定到 Threads,以下所示:
// Setup ThreadLocal of Kryo instances private static final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; }; }; // Somewhere else, use Kryo Kryo k = kryos.get(); ...
或者您也可使用 kryo 提供的 KryoPool。 KryoPool 容許使用 SoftReferences 保留對 Kryo 實例的引用,這樣當 JVM 開始耗盡內存時,Kryo 實例就能夠被 GC 回收(固然你也可使用 ThreadLocal 和 SoftReferences)。
如下是一個示例,顯示如何使用KryoPool:
import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.pool.*; KryoFactory factory = new KryoFactory() { public Kryo create () { Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; } }; // Build pool with SoftReferences enabled (optional) KryoPool pool = new KryoPool.Builder(factory).softReferences().build(); Kryo kryo = pool.borrow(); // do s.th. with kryo here, and afterwards release it pool.release(kryo); // or use a callback to work with kryo - no need to borrow/release, // that's done by `run`. String value = pool.run(new KryoCallback() { public String execute(Kryo kryo) { return kryo.readObject(input, String.class); } });
Kryo利用低開銷,輕量級的MinLog日誌庫。能夠經過如下方法之一設置日誌記錄級別:
Log.ERROR(); Log.WARN(); Log.INFO(); Log.DEBUG(); Log.TRACE();
Kryo在INFO(默認)和以上級別沒有記錄。 DEBUG方便在開發過程當中使用。調試一個特定的問題時,TRACE很好用,可是一般輸出的信息太多。
MinLog支持固定的日誌記錄級別,這會致使javac在編譯時刪除低於該級別的日誌記錄。在Kryo發行版ZIP中,"debug"JAR啓用日誌記錄。 "production"JAR使用NONE的固定日誌記錄級別,這意味着全部日誌記錄代碼已被刪除。
請參閱如下爲Scala類提供序列化的項目:
請參閱如下項目,它是Kryo的Objective-C端口:
能夠將 Kryo 與 JVM Serializers 項目中的許多其餘序列化庫進行比較。使用基準測試很難完全比較序列化庫。這些序列化庫有不一樣的目的,並擅長徹底不一樣的問題。爲了理解這些基準測試,運行代碼和序列化的數據應該根據您的具體需求進行分析和對比。一些序列化程序是高度優化的,代碼可能多達幾頁,而有些僅只有幾行。這很好的說明了有些測試可能有用,但在許多狀況下可能並不實用。
「kryo」是典型的 Kryo 使用方式,類是註冊的,序列化自動完成。 「kryo-opt」顯示瞭如何配置序列化器以減小序列化數據的大小,但序列化仍然是自動完成的。 「kryo-manual」壽命瞭如何使用手寫的序列化代碼來優化大小和速度,同時仍然利用 Kryo 進行大部分工做。
有很多項目使用了Kryo,如下僅列出一些。若是您但願您的項目也列在此,請提交一個申請。