Kryo 是一個快速且有效率的 Java 二進位物件圖序列化框架。該專案的目標是高速、小尺寸和易於使用的 API。任何時候需要持久保存物件時,無論是保存到檔案、資料庫或透過網絡,該專案都很有用。
Kryo 還可以執行自動深淺複製/克隆。這是從物件到物件的直接複製,而不是物件到位元組到物件的複製。
本文檔適用於 Kryo 版本 5.x。請參閱 Wiki 以了解版本 4.x。
請使用 Kryo 郵件清單來尋求問題、討論和支援。請將 Kryo 問題追蹤器的使用限制為錯誤和增強功能,而不是問題、討論或支援。
Kryo 發布兩種工件/jar:
Kryo JAR 可在發布頁面和 Maven Central 上找到。 Kryo 的最新快照(包括 master 的快照版本)位於 Sonatype 儲存庫中。
若要在應用程式中使用最新的 Kryo 版本,請在pom.xml
中使用此依賴項條目:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
若要在要發佈的庫中使用最新的 Kryo 版本,請在pom.xml
中使用此相依性條目:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
若要使用最新的 Kryo 快照,請使用:
< repository >
< id >sonatype-snapshots</ id >
< name >sonatype snapshots repo</ name >
< url >https://oss.sonatype.org/content/repositories/snapshots</ url >
</ repository >
<!-- for usage in an application: -->
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
<!-- for usage in a library that should be published: -->
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
並不是每個人都是 Maven 粉絲。在沒有 Maven 的情況下使用 Kryo 需要將 Kryo JAR 以及 lib 中找到的依賴 JAR 放在類別路徑上。
從原始碼建置 Kryo 需要 JDK11+ 和 Maven。若要建置所有工件,請執行:
mvn clean && mvn install
繼續展示如何使用該庫:
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 ();
}
static public class SomeClass {
String value ;
}
}
Kryo 類別自動執行序列化。輸出和輸入類別處理緩衝位元組並可選擇刷新到流。
本文檔的其餘部分詳細介紹了其工作原理以及該庫的高級用法。
使用輸入和輸出類別將資料傳入和傳出 Kryo。這些類別不是線程安全的。
Output 類別是將資料寫入位元組數組緩衝區的 OutputStream。如果需要位元組數組,可以直接取得並使用該緩衝區。如果為 Output 提供一個 OutputStream,則當緩衝區已滿時,它會將位元組刷新到流中,否則 Output 可以自動增長其緩衝區。 Output 有許多方法可以有效地將原語和字串寫入位元組。它提供與 DataOutputStream、BufferedOutputStream、FilterOutputStream 和 ByteArrayOutputStream 類似的功能,全部在一個類別中。
提示:Output 和Input 提供ByteArrayOutputStream 的所有功能。很少有理由將輸出刷新到 ByteArrayOutputStream。
Output 在寫入 OutputStream 時會緩衝位元組,因此寫入完成後必須呼叫flush
或close
才能將緩衝的位元組寫入 OutputStream。如果Output 尚未提供OutputStream,則無需呼叫flush
或close
。與許多流不同,Output 實例可以透過設定位置或設定新的位元組數組或流來重複使用。
提示:由於輸出已緩衝,因此沒有理由將輸出刷新到 BufferedOutputStream。
零參數輸出建構函式建立一個未初始化的輸出。在使用輸出之前必須呼叫輸出setBuffer
。
Input 類別是從位元組數組緩衝區讀取資料的InputStream。如果需要從位元組數組讀取,可以直接設定該緩衝區。如果輸入有一個InputStream,當緩衝區中的所有資料都被讀取後,它將從流中填充緩衝區。輸入有許多方法可以有效地從位元組讀取原語和字串。它提供與 DataInputStream、BufferedInputStream、FilterInputStream 和 ByteArrayInputStream 類似的功能,所有功能都在一個類別中。
提示:Input 提供了 ByteArrayInputStream 的所有功能。很少有理由從 ByteArrayInputStream 讀取輸入。
如果呼叫了Input close
,則關閉該Input 的InputStream(如果有)。如果不從 InputStream 讀取,則無需呼叫close
。與許多流不同,Input 實例可以透過設定位置和限製或設定新的位元組數組或 InputStream 來重複使用。
零參數輸入建構函式建立一個未初始化的輸入。在使用輸入之前必須呼叫輸入setBuffer
。
ByteBufferOutput 和 ByteBufferInput 類別的工作方式與 Output 和 Input 完全相同,只是它們使用 ByteBuffer 而不是位元組數組。
UnsafeOutput、UnsafeInput、UnsafeByteBufferOutput 和 UnsafeByteBufferInput 類別的工作方式與其非不安全對應類別完全相同,只不過它們在許多情況下使用 sun.misc.Unsafe 來獲得更高的效能。要使用這些類, Util.unsafe
必須為 true。
使用不安全緩衝區的缺點是執行序列化的系統的本機位元組序和數字類型的表示會影響序列化資料。例如,如果資料在X86上寫入並在SPARC上讀取,則反序列化將會失敗。此外,如果使用不安全的緩衝區寫入數據,則必須使用不安全的緩衝區讀取資料。
不安全緩衝區的最大效能差異是在不使用可變長度編碼時使用大型原始陣列。可以針對不安全的緩衝區或僅針對特定欄位停用可變長度編碼(使用 FieldSerializer 時)。
IO 類別提供了讀取和寫入可變長度 int (varint) 和 long (varlong) 值的方法。這是透過使用每個位元組的第 8 位元來指示後面是否有更多位元組來完成的,這意味著 varint 使用 1-5 個位元組,varlong 使用 1-9 個位元組。使用可變長度編碼成本更高,但會使序列化資料更小。
寫入可變長度值時,可以針對正值或負值和正值最佳化該值。例如,當針對正值進行最佳化時,0 到 127 寫入一個位元組,128 到 16383 寫入兩個位元組等。當未針對正值進行最佳化時,這些範圍會下移一半。例如,-64到63寫在一個位元組中,64到8191和-65到-8192寫在兩個位元組中,等等。
輸入和輸出緩衝區提供了讀取和寫入固定大小或可變長度值的方法。還有一些方法允許緩衝區決定是寫入固定大小還是可變長度值。這允許序列化程式碼確保可變長度編碼用於非常常見的值,如果使用固定大小,這些值會使輸出膨脹,同時仍允許緩衝區配置決定所有其他值。
方法 | 描述 |
---|---|
寫Int(int) | 寫入 4 位元組 int。 |
writeVarInt(int, 布林值) | 寫入 1-5 位元組 int。 |
writeInt(int, 布林值) | 寫入 4 或 1-5 位元組 int(由緩衝區決定)。 |
寫長(長) | 寫入 8 位元組長。 |
writeVarLong(長整型,布林值) | 寫入 1-9 個位元組長。 |
writeLong(長,布林值) | 寫入 8 或 1-9 位元組長(由緩衝區決定)。 |
要停用所有值的可變長度編碼,需要重寫writeVarInt
、 writeVarLong
、 readVarInt
和readVarLong
方法。
先寫入一些數據的長度,然後再寫入數據,這可能很有用。當事先不知道資料的長度時,需要將所有資料快取起來以確定其長度,然後才能寫入長度,然後寫入資料。為此使用單一大緩衝區會阻止串流傳輸,並且可能需要不合理的大緩衝區,這並不理想。
分塊編碼透過使用小緩衝區解決了這個問題。當緩衝區已滿時,先寫入其長度,然後寫入資料。這是一大塊數據。緩衝區被清除,這種情況將持續到沒有更多資料可寫入為止。長度為零的塊表示塊的結尾。
Kryo 提供了一些類別來使分塊編碼易於使用。 OutputChunked 用於寫入分塊資料。它擴展了 Output,因此擁有所有寫入資料的便捷方法。當 OutputChunked 緩衝區已滿時,它會將區塊刷新到另一個 OutputStream。 endChunk
方法用來標記一組區塊的結尾。
OutputStream outputStream = new FileOutputStream ( "file.bin" );
OutputChunked output = new OutputChunked ( outputStream , 1024 );
// Write data to output...
output . endChunk ();
// Write more data to output...
output . endChunk ();
// Write even more data to output...
output . endChunk ();
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 ();
一般來說,輸出和輸入提供良好的性能。如果不安全緩衝區的跨平台不相容性是可以接受的,那麼它們的性能同樣好甚至更好,特別是對於原始數組。 ByteBufferOutput 和 ByteBufferInput 提供的效能稍差,但如果位元組的最終目的地必須是 ByteBuffer,則這可能是可以接受的。
可變長度編碼比固定值慢,尤其是當有大量資料使用它時。
分塊編碼使用中間緩衝區,因此它會新增所有位元組的額外副本。僅此一點可能是可以接受的,但是當在可重入序列化器中使用時,序列化器必須為每個物件建立一個 OutputChunked 或 InputChunked。在序列化期間分配和垃圾收集這些緩衝區可能會對效能產生負面影響。
Kryo 具有三組讀取和寫入物件的方法。如果物件的特定類別未知且該物件可能為空:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
如果類別已知且物件可能為 null:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
如果類別是已知的且物件不能為空:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
所有這些方法首先找到要使用的適當的序列化器,然後使用它來序列化或反序列化物件。序列化器可以呼叫這些方法進行遞歸序列化。 Kryo 自動處理對同一物件的多個參考和循環參考。
除了讀取和寫入物件的方法之外,Kryo 類別還提供了一種註冊序列化器、高效讀取和寫入類別標識符、處理不能接受空值的序列化器的空物件以及處理讀取和寫入物件參考(如果啟用)的方法。這使得序列化器可以專注於其序列化任務。
在測試和探索 Kryo API 時,將物件寫入字節,然後將這些位元組讀回物件可能會很有用。
Kryo kryo = new Kryo ();
// Register all classes to be serialized.
kryo . register ( SomeClass . class );
SomeClass object1 = new SomeClass ();
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , object1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
SomeClass object2 = kryo . readObject ( input , SomeClass . class );
在此範例中,輸出從容量為 1024 位元組的緩衝區開始。如果將更多位元組寫入輸出,緩衝區的大小將無限制地增長。 Output 不需要關閉,因為它還沒有被賦予 OutputStream。輸入直接從輸出的byte[]
緩衝區讀取。
Kryo 支援使用從一個物件到另一個物件的直接分配來製作物件的深拷貝和淺拷貝。這比序列化為位元組然後返回物件更有效。
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
所有正在使用的序列化器都需要支援複製。 Kryo 提供的所有序列化器都支援複製。
與序列化一樣,複製時,如果啟用了引用,Kryo 會自動處理對同一物件的多個引用和循環引用。
如果僅使用 Kryo 進行複製,則可以安全地停用註冊。
複製物件圖後可以使用 Kryo getOriginalToCopyMap
來取得舊物件到新物件的對應。地圖由 Kryo reset
自動清除,因此僅當 Kryo setAutoReset
為 false 時才有用。
預設情況下不啟用引用。這意味著如果一個物件多次出現在物件圖中,它將被多次寫入,並將被反序列化為多個不同的物件。當引用被停用時,循環引用將導致序列化失敗。使用用於序列化的 Kryo setReferences
和用於複製的setCopyReferences
啟用或停用引用。
啟用引用後,當每個物件第一次出現在物件圖中時,會在每個物件之前寫入一個 varint。對於同一物件圖中該類別的後續出現,僅寫入 varint。反序列化後,物件參考將恢復,包括任何循環引用。使用的序列化器必須透過在 Serializer read
中呼叫 Kryo reference
來支援引用。
啟用引用會影響效能,因為需要追蹤讀取或寫入的每個物件。
在幕後,ReferenceResolver 處理已讀取或寫入的追蹤物件並提供 int 參考 ID。提供了多種實作方式:
ReferenceResolver useReferences(Class)
可以被重寫。它傳回一個布林值來決定類別是否支援引用。如果類別不支援引用,則 varint 引用 ID 不會寫在該類型的物件之前。如果某個類別不需要引用,並且該類型的物件多次出現在物件圖中,則可以透過停用該類別的引用來大幅減小序列化大小。預設引用解析器為所有原始包裝器和枚舉傳回 false。對於 String 和其他類,通常也會傳回 false,這取決於要序列化的物件圖。
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
引用解析器確定單一物件圖中引用的最大數量。 Java 陣列索引僅限於Integer.MAX_VALUE
,因此使用基於陣列的資料結構的參考解析器在序列化超過約 20 億個物件時可能會導致java.lang.NegativeArraySizeException
。 Kryo 使用 int 類別 ID,因此單一物件圖中的最大引用數量僅限於 int 中的全部正數和負數(約 40 億)。
Kryo getContext
傳回一個用於儲存使用者資料的映射。 Kryo 實例可供所有序列化程式使用,因此所有序列化程式都可以輕鬆存取此資料。
Kryo getGraphContext
類似,但在每個物件圖序列化或反序列化後被清除。這使得管理僅與當前物件圖相關的狀態變得容易。例如,這可以用於在物件圖中第一次遇到類別時寫入一些模式資料。有關範例,請參閱 CompatibleFieldSerializer。
預設情況下,在每個整個物件圖序列化後調用 Kryo reset
。這將重置類別解析器中未註冊的類別名稱、引用解析器中對先前序列化或反序列化物件的引用,並清除圖形上下文。 Kryo setAutoReset(false)
可用於停用自動呼叫reset
,從而允許該狀態跨越多個物件圖。
Kryo 是一個促進序列化的架構。框架本身不強制執行模式,也不關心資料寫入或讀取的內容或方式。序列化器是可插入的,並決定讀取和寫入什麼。許多串列器都是開箱即用的,可以以各種方式讀取和寫入資料。雖然提供的序列化器可以讀取和寫入大多數對象,但它們可以輕鬆地用您自己的序列化器部分或完全替換。
當 Kryo 編寫物件的實例時,首先它可能需要編寫一些標識該物件的類別的內容。預設情況下,Kryo 將讀取或寫入的所有類別都必須事先註冊。註冊提供了一個 int 類別 ID、用於該類別的序列化器以及用於建立該類別實例的物件實例化器。
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
在反序列化期間,註冊的類別必須具有與序列化期間完全相同的 ID。註冊時,類別會被指派下一個可用的最小整數 ID,這表示類別的註冊順序很重要。可以選擇明確指定類別 ID,以使順序變得不重要:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
類別 ID -1 和 -2 被保留。預設情況下,類別 ID 0-8 用於基本類型和字串,但這些 ID 可以改變用途。 ID 被寫為正最佳化變體,因此當它們是小的正整數時最有效。負 ID 無法有效序列化。
在幕後,ClassResolver 實際處理讀取和寫入位元組來表示類別。預設實作在大多數情況下就足夠了,但可以替換它來自訂註冊類別時發生的情況、序列化期間遇到未註冊的類別時發生的情況以及代表類別時讀取和寫入的內容。
Kryo 可以配置為允許序列化,而無需預先註冊類別。
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
可混合使用註冊類別和未註冊類別。未註冊的類別有兩個主要缺點:
如果僅使用 Kryo 進行複製,則可以安全地停用註冊。
當不需要註冊時,可以啟用 Kryo setWarnUnregisteredClasses
在遇到未註冊的類別時記錄訊息。這可用於輕鬆取得所有未註冊類別的清單。可以重寫 Kryo unregisteredClassMessage
以自訂日誌訊息或採取其他操作。
註冊類別時,可以選擇指定序列化器實例。在反序列化期間,註冊的類別必須具有與序列化期間完全相同的序列化器和序列化器配置。
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
如果未指定序列化器或遇到未註冊的類,則會從將類別對應到序列化器的「預設序列化器」清單中自動選擇序列化器。擁有許多預設序列化器不會影響序列化效能,因此預設情況下,Kryo 為各種 JRE 類別提供 50 多個預設序列化器。可以添加其他預設序列化器:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
這將導致在註冊 SomeClass 或任何擴展或實作 SomeClass 的類別時建立 SomeSerializer 實例。
預設序列化器經過排序,因此首先匹配更具體的類,但否則按照添加順序進行匹配。它們的新增順序可能與介面相關。
如果沒有預設序列化器與類別匹配,則使用全域預設序列化器。全域預設序列化器預設為 FieldSerializer,但可以變更。通常,全域序列化器可以處理多種不同類型。
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
對於此程式碼,假設沒有預設序列化器與 SomeClass 匹配,則將使用 TaggedFieldSerializer。
類別也可以使用 DefaultSerializer 註釋,該註釋將用於代替選擇 Kryo 的預設序列化器之一:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
為了獲得最大的靈活性,可以重寫 Kryo getDefaultSerializer
以實現用於選擇和實例化序列化器的自訂邏輯。
addDefaultSerializer(Class, Class)
方法不允許配置序列化器。可以設定序列化器工廠而不是序列化器類,從而允許工廠創建和配置每個序列化器實例。為常見的序列化器提供工廠,通常使用getConfig
方法來配置已建立的序列化器。
Kryo kryo = new Kryo ();
TaggedFieldSerializerFactory defaultFactory = new TaggedFieldSerializerFactory ();
defaultFactory . getConfig (). setReadUnknownTagData ( true );
kryo . setDefaultSerializer ( defaultFactory );
FieldSerializerFactory someClassFactory = new FieldSerializerFactory ();
someClassFactory . getConfig (). setFieldsCanBeNull ( false );
kryo . register ( SomeClass . class , someClassFactory );
序列化器工廠有一個isSupported(Class)
方法,該方法允許它拒絕處理某個類,即使它與該類別相符。這允許工廠檢查多個介面或實現其他邏輯。
雖然某些序列化器適用於特定類,但其他序列化器可以序列化許多不同的類別。序列化器可以使用 Kryo newInstance(Class)
建立任何類別的實例。這是透過尋找類別的註冊,然後使用註冊的 ObjectInstantiator 來完成的。實例化器可以在註冊時指定。
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
如果註冊沒有實例化器,則由 Kryo newInstantiator
提供。若要自訂物件的建立方式,可以覆寫 Kryo newInstantiator
或提供 InstantiatorStrategy。
Kryo 提供了 DefaultInstantiatorStrategy,它使用 ReflectASM 建立物件來呼叫零參數建構函式。如果不可能,它會使用反射來呼叫零參數建構函數。如果也失敗,那麼它要么拋出異常,要么嘗試後備 InstantiatorStrategy。反射使用setAccessible
,因此私有零參數建構子是允許 Kryo 建立類別實例而不影響公用 API 的好方法。
DefaultInstantiatorStrategy 是使用 Kryo 創建物件的推薦方法。它運行建構函式就像使用 Java 程式碼一樣。另外,語言外的機制也可以用來創造物件。 Objenesis StdInstantiatorStrategy 使用 JVM 特定的 API 來建立類別的實例,而根本不需要呼叫任何建構子。使用它是危險的,因為大多數類別都希望呼叫它們的建構子。透過繞過建構函式來建立物件可能會使物件處於未初始化或無效狀態。類別必須設計為以這種方式創建。
Kryo 可以設定為先嘗試 DefaultInstantiatorStrategy,然後在必要時回退到 StdInstantiatorStrategy。
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
另一個選擇是 SerializingInstantiatorStrategy,它使用 Java 內建的序列化機制來建立實例。使用此方法,類別必須實作 java.io.Serializable 並呼叫超類別中的第一個零參數建構函式。這也會繞過構造函數,因此是危險的,原因與 StdInstantiatorStrategy 相同。
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
或者,一些通用序列化程式提供可以重寫的方法來自訂特定類型的物件創建,而不是呼叫 Kryo newInstance
。
kryo . register ( SomeClass . class , new FieldSerializer ( kryo , SomeClass . class ) {
protected T create ( Kryo kryo , Input input , Class <? extends T > type ) {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
一些序列化程式提供了writeHeader
方法,可以重寫該方法以在正確的時間寫入create
所需的資料。
static public class TreeMapSerializer extends MapSerializer < TreeMap > {
protected void writeHeader ( Kryo kryo , Output output , TreeMap map ) {
kryo . writeClassAndObject ( output , map . comparator ());
}
protected TreeMap create ( Kryo kryo , Input input , Class <? extends TreeMap > type , int size ) {
return new TreeMap (( Comparator ) kryo . readClassAndObject ( input ));
}
}
如果序列化器不提供writeHeader
,則可以在write
中為create
寫入資料。
static public class SomeClassSerializer extends FieldSerializer < SomeClass > {
public SomeClassSerializer ( Kryo kryo ) {
super ( kryo , SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
output . writeInt ( object . value );
}
protected SomeClass create ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
return new SomeClass ( input . readInt ());
}
}
即使序列化程序知道值的預期類別(例如欄位的類別),如果該值的特定類別不是最終的,則序列化程式需要先寫入類別 ID,然後寫入值。最終類別可以更有效地序列化,因為它們是非多態的。
Kryo isFinal
用於確定一個類別是否為最終類別。即使對於非最終類型,也可以重寫此方法以傳回 true。例如,如果應用程式廣泛使用 ArrayList 但從不使用 ArrayList 子類,則將 ArrayList 視為 Final 可以允許 FieldSerializer 為每個 ArrayList 欄位節省 1-2 個位元組。
Kryo 可以序列化實作 java.io.Serialized 的 Java 8+ 閉包,但有一些注意事項。在一個 JVM 上序列化的閉包可能無法在另一個 JVM 上反序列化。
Kryo isClosure
用於確定一個類別是否為閉包。如果是這樣,則使用 ClosureSerializer.Closure 來尋找類別註冊而不是閉包的類別。要序列化閉包,必須註冊以下類別:ClosureSerializer.Closure、Object[] 和 Class。此外,必須註冊閉包的捕獲類別。
kryo . register ( Object []. class );
kryo . register ( Class . class );
kryo . register ( ClosureSerializer . Closure . class , new ClosureSerializer ());
kryo . register ( CapturingClass . class );
Callable < Integer > closure1 = ( Callable < Integer > & java . io . Serializable )( () -> 72363 );
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , closure1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
Callable < Integer > closure2 = ( Callable < Integer >) kryo . readObject ( input , ClosureSerializer . Closure . class );
透過一些努力,可以序列化未實現 Serialized 的閉包。
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。這些序列化器包裝另一個序列化器來編碼和解碼位元組。
Serializer 抽象類別定義了從物件到位元組以及從位元組到物件的方法。
public class ColorSerializer extends Serializer < Color > {
public void write ( Kryo kryo , Output output , Color color ) {
output . writeInt ( color . getRGB ());
}
public Color read ( Kryo kryo , Input input , Class <? extends Color > type ) {
return new Color ( input . readInt ());
}
}
序列化器只有兩個必須實現的方法。 write
將物件作為位元組寫入輸出。 read
創建物件的新實例並從輸入中讀取以填充它。
當 Kryo 用於在 Serializer read
中讀取巢狀物件時,如果巢狀物件可以引用父對象,則必須先使用父物件呼叫 Kryo reference
。如果巢狀物件不可能引用父物件、如果 Kryo 未用於巢狀物件或未使用引用,則無需呼叫 Kryo reference
。如果嵌套物件可以使用相同的序列化程序,則該序列化程序必須是可重入的。
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
SomeClass object = new SomeClass ();
kryo . reference ( object );
// Read objects that may reference the SomeClass instance.
object . someField = kryo . readClassAndObject ( input );
return object ;
}
序列化器通常不應直接使用其他序列化器,而應使用 Kryo 讀寫法。這允許 Kryo 編排序列化並處理引用和空物件等功能。有時,序列化程序知道對嵌套物件使用哪個序列化程序。在這種情況下,它應該使用 Kryo 接受序列化器的讀寫方法。
如果物件可以為空:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
如果物件不能為空:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
在序列化期間,Kryo getDepth
提供物件圖的當前深度。
當序列化失敗時,可以拋出 KryoException 以及有關物件圖中異常發生位置的序列化追蹤資訊。使用嵌套序列化器時,可以捕獲 KryoException 以添加序列化追蹤資訊。
Object object = ...
Field [] fields = ...
for ( Field field : fields ) {
try {
// Use other serializers to serialize each field.
} catch ( KryoException ex ) {
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
} catch ( Throwable t ) {
KryoException ex = new KryoException ( t );
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
}
}
Kryo 提供的序列化程序在序列化巢狀物件時使用呼叫堆疊。 Kryo 最大限度地減少了堆疊調用,但對於極深的物件圖可能會發生堆疊溢位。這是大多數序列化函式庫(包括內建 Java 序列化)的常見問題。可以使用-Xss
增加堆疊大小,但請注意,這適用於所有執行緒。具有許多執行緒的 JVM 中的大堆疊可能會使用大量記憶體。
Kryo setMaxDepth
可用於限制物件圖的最大深度。這可以防止惡意資料導致堆疊溢位。
預設情況下,序列化器永遠不會收到 null,而是 Kryo 會根據需要寫入一個位元組來表示 null 或非 null。如果序列化程式可以透過本身處理 null 來提高效率,則它可以呼叫 Serializer setAcceptsNull(true)
。當已知序列化器將處理的所有實例永遠不會為空時,這也可以用於避免寫入表示位元組的空值。
Kryo getGenerics
提供泛型類型信息,因此序列化器可以更有效率。這最常用於避免在類型參數類別為最終類型時編寫類別。
通用類型推斷預設啟用,可以使用 Kryo setOptimizedGenerics(false)
停用。禁用泛型最佳化可以提高效能,但代價是序列化大小更大。
如果該類別具有單一類型參數, nextGenericClass
會傳回類型參數類,如果沒有,則傳回 null。讀取或寫入任何巢狀物件後,必須呼叫popGenericType
。有關範例,請參閱 CollectionSerializer。
public class SomeClass < T > {
public T value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
對於具有多個類型參數的類, nextGenericTypes
傳回 GenericType 實例的數組,並使用resolve
來取得每個 GenericType 的類別。讀取或寫入任何巢狀物件後,必須呼叫popGenericType
。有關範例,請參閱 MapSerializer。
public class SomeClass < K , V > {
public K key ;
public V value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
kryo . writeObjectOrNull ( output , object . key , serializer );
} else
kryo . writeClassAndObject ( output , object . key );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
object . key = kryo . readObjectOrNull ( input , keyClass , serializer );
} else
object . key = kryo . readClassAndObject ( input );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
對於傳遞物件圖中嵌套物件的類型參數資訊的序列化程序(有些高階用法),首先使用 GenericsHierarchy 來儲存類別的類型參數。在序列化期間,在解析泛型類型(如果有)之前呼叫泛型pushTypeVariables
。如果回傳 >0,則後面必須跟泛型popTypeVariables
。有關範例,請參閱 FieldSerializer。
public class SomeClass < T > {
T value ;
List < T > list ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
private final GenericsHierarchy genericsHierarchy ;
public SomeClassSerializer () {
genericsHierarchy = new GenericsHierarchy ( SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . writeClassAndObject ( output , object . list );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
object . list = ( List ) kryo . readClassAndObject ( input );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
return object ;
}
}
類別可以選擇透過實作 KryoSerialized(類似 java.io.Externalizable)來進行自己的序列化,而不是使用序列化器。
public class SomeClass implements KryoSerializable {
private int value ;
public void write ( Kryo kryo , Output output ) {
output . writeInt ( value , false );
}
public void read ( Kryo kryo , Input input ) {
value = input . readInt ( false );
}
}
顯然,在呼叫read
之前必須已經創建實例,因此該類別無法控制自己的創建。 KryoSerialized 類別將使用預設序列化器 KryoSerializedSerializer,它使用 Kryo newInstance
建立新實例。編寫自己的序列化程式來自訂流程、在序列化之前或之後呼叫方法等都很簡單。
僅當copy
被覆蓋時,序列化程序才支援複製。與 Serializer read
類似,此方法包含建立和配置副本的邏輯。就像read
一樣,如果任何子對象可以引用父對象,則必須在使用 Kryo 複製子reference
之前呼叫 Kryo 引用。
class SomeClassSerializer extends Serializer < SomeClass > {
public SomeClass copy ( Kryo kryo , SomeClass original ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = original . intValue ;
copy . object = kryo . copy ( original . object );
return copy ;
}
}
類別可以實作 KryoCopyable 來進行自己的複製,而不是使用序列化器:
public class SomeClass implements KryoCopyable < SomeClass > {
public SomeClass copy ( Kryo kryo ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = intValue ;
copy . object = kryo . copy ( object );
return copy ;
}
}
當類型不可變時,可以使用序列化器setImmutable(true)
。在這種情況下,不需要實作序列化器copy
——預設的複製實copy
將傳回原始物件。
以下經驗規則適用於 Kryo 的版本編號:
升級任何依賴性是一個重要的事件,但是序列化庫比大多數依賴性更容易破裂。升級Kryo時,請檢查版本差異並在您自己的應用程式中徹底測試新版本。我們試圖盡可能安全和容易。
預設提供的Kryo序列化假定Java將用於避難所,因此它們不會明確定義所編寫的格式。可以使用標準化格式編寫序列化器,該格式更容易由其他語言讀取,但預設不會提供。
對於某些需求,例如序列化位元組的長期存儲,序列化處理類別的變化可能很重要。這被稱為正向相容性(透過較新的類別序列化的讀取位元組)和向後相容性(讀取位元組由較舊類別序列化)。 Kryo提供了一些通用序列化器,這些序列化以處理相容性採取不同的方法。可以輕鬆地開發其他序列化器,以用於前向和向後相容,例如使用外部,手寫模式的序列化器。
當類別的變更超過其序列化器所能處理的更多時,可以寫入序列化器以將資料傳輸到其他類別。應用程式程式碼中舊類別的所有使用都應由新類別取代。舊課程僅用於此序列化器。
kryo . register ( OldClass . class , new TaggedFieldSerializer ( kryo , OldClass . class ) {
public Object read ( Kryo kryo , Input input , Class type ) {
OldClass oldObject = ( OldClass ) super . read ( kryo , input , OldClass . class );
NewClass newObject = new NewClass ();
// Use data from the old class to populate the instance of the new class and return it.
return newObject ;
}
});
kryo . register ( NewClass . class );
Kryo為許多序列化提供了各種配置選項和相容性等級。在Kryo-Serializers姊妹專案中可以找到其他串行序列,該專案託管了存取私有API的串行序列,或在所有JVM上都不是完全安全的。可以在連結部分中找到更多的序列化器。
FieldSerializer透過序列化每個非傳輸欄位來運作。它可以在沒有任何配置的情況下序列化Pojos和許多其他類別。預設情況下,所有非公共欄位都寫入和閱讀,因此評估將要序列化的每個類別都很重要。如果欄位是公開的,則序列化可能會更快。
FieldSerializer僅透過使用Java類別檔案作為架構編寫欄位數據,而無需編寫欄位數據,這是有效的。它不支援添加,刪除或更改字段類型,而不會使先前序列化位元組的無效。僅當它不更改欄位的字母順序時,才允許重命名欄位。
在許多情況下,例如透過網路傳送資料時,可以接受FieldSerializer的相容性缺點,但對於長期資料儲存而言可能不是一個不錯的選擇,因為Java類別無法發展。在許多情況下,TaggedFieldSerializer是更好的選擇。
環境 | 描述 | 預設值 |
---|---|---|
fieldsCanBeNull | 假定沒有欄位值為null時,每個欄位可以節省0-1位元組。 | 真的 |
setFieldsAsAccessible | 如果是,如有必要,所有非傳播欄位(包括私有欄位)將被序列化和setAccessible 。如果是錯誤的,則僅將公共API中的欄位序列化。 | 真的 |
ignoreSyntheticFields | 如果是正確的,則將合成欄位(由編譯器產生用於範圍的範圍)序列化。 | 錯誤的 |
fixedFieldTypes | 如果為true,則假定每個欄位值的混凝土類型都符合該欄位的類型。這消除了為欄位值編寫類別ID的需要。 | 錯誤的 |
copyTransient | 如果為true,將複製所有瞬態欄位。 | 真的 |
serializeTransient | 如果是真的,則瞬態欄位將被序列化。 | 錯誤的 |
variableLengthEncoding | 如果為true,則將可變長度值用於INT和長欄位。 | 真的 |
extendedFieldNames | 如果為true,則欄位名稱由其聲明的類別前綴。當子類別具有與超級類別相同名稱的欄位時,這可以避免衝突。 | 錯誤的 |
FieldSerializer提供將要序列化的欄位。可以刪除字段,因此不會被序列化。可以將欄位配置為使序列化效率更高。
FieldSerializer fieldSerializer = ...
fieldSerializer . removeField ( "id" ); // Won't be serialized.
CachedField nameField = fieldSerializer . getField ( "name" );
nameField . setCanBeNull ( false );
CachedField someClassField = fieldSerializer . getField ( "someClass" );
someClassField . setClass ( SomeClass . class , new SomeClassSerializer ());
環境 | 描述 | 預設值 |
---|---|---|
canBeNull | 假定為false時,欄位值永遠不會為空,可以節省0-1位元組。 | 真的 |
valueClass | 將混凝土類別和序列化器設定為用於欄位值。這消除了為值編寫類別ID的需要。如果欄位值的類別是原始的,原始的包裝器或最終,則此設定預設為欄位的類別。 | 無效的 |
serializer | 設定用於欄位值的序列化器。如果設定了序列化器,則某些序列化也需要設定值類別。如果為NULL,將使用在KRYO中註冊的磁場值類別的序列化器。 | 無效的 |
variableLengthEncoding | 如果為true,則使用可變長度值。這僅適用於INT或長字段。 | 真的 |
optimizePositive | 如果為真,則對可變長度值的正值進行最佳化。當使用可變長度編碼時,這僅適用於INT或長字段。 | 真的 |
註釋可用於為每個欄位配置序列化。
註解 | 描述 |
---|---|
@Bind | 為任何字段設定快取的設定。 |
@CollectionBind | 為收集字段設定CollectionSerializer設定。 |
@MapBind | 為地圖字段設定映射器設定。 |
@NotNull | 將一個領域標記為永不為空的。 |
public class SomeClass {
@ NotNull
@ Bind ( serializer = StringSerializer . class , valueClass = String . class , canBeNull = false )
Object stringField ;
@ Bind ( variableLengthEncoding = false )
int intField ;
@ BindMap (
keySerializer = StringSerializer . class ,
valueSerializer = IntArraySerializer . class ,
keyClass = String . class ,
valueClass = int []. class ,
keysCanBeNull = false )
Map map ;
@ BindCollection (
elementSerializer = LongArraySerializer . class ,
elementClass = long []. class ,
elementsCanBeNull = false )
Collection collection ;
}
版本FieldSerializer擴展了FieldSerializer,並提供了向後相容性。這意味著可以添加字段而不會使先前序列化位元組的無效。不支援刪除,重新命名或更改欄位類型。
新增欄位時,必須具有@Since(int)
註解以指示其新增的版本,以便與先前序列化的位元組相容。註釋值永遠不變。
版本FieldSerializer為FieldSerializer添加了很少的開銷:一個附加的Varint。
環境 | 描述 | 預設值 |
---|---|---|
compatible | 當false讀取具有不同版本的物件時,會拋出異常。物件的版本是任何欄位的最大版本。 | 真的 |
versionFieldSerializer也繼承了FieldSerializer的所有設定。
TaggedFieldSerializer擴展了FieldSerializer,以提供向後相容性和可選的向前相容性。這意味著可以添加或重命名和可選地刪除字段,而不會使先前序列化位元組的無效。不支援更改字段的類型。
只有具有@Tag(int)
註解的欄位才能序列化。欄位標籤值必須在類別及其所有超級類別中唯一。如果遇到重複的標籤值,則會拋出例外狀況。
向前和向後的相容性和序列化效能取決於readUnknownTagData
和chunkedEncoding
設定。此外,在每個欄位之前為標籤值寫一個varint。
當readUnknownTagData
和chunkedEncoding
為False時,必須刪除字段,但可以套用@Deprecated
註解。讀取舊位元組時讀取折衷的字段,但沒有寫入新位元組。可以透過閱讀棄用欄位的價值並在其他地方編寫課程來發展。可以將欄位重新命名和/或私有以減少班級的混亂(例如, ignored1
, ignored2
)。
TaggedFieldSerializer(帶有readUnknownTagData
和chunkedEncoding
False)是大多數可以註釋字段的類別的序列化器。它允許類別從序列化資料(透過棄用)中刪除類,並且可以將欄位刪除,滿足大多數應用程式的需求,而不會增加序列化大小。
環境 | 描述 | 預設值 |
---|---|---|
readUnknownTagData | 當遇到錯誤和未知標籤時,會拋出異常,或者,如果chunkedEncoding 為真,則跳過了資料。如果為true,則每個欄位值的類別是在值之前寫入的。當遇到未知標籤時,嘗試讀取資料。這用於跳過數據,如果啟用了引用,則物件圖中的任何其他值仍然可以被序列化。如果讀取資料失敗(例如,類別是未知或已刪除的),則會引發異常,或者,如果 chunkedEncoding 為真,則跳過資料。無論哪種情況,如果跳過資料並啟用了引用,則不會讀取跳過資料中的任何引用,並且進一步的避免序列化可能會收到錯誤的參考和失敗。 | 錯誤的 |
chunkedEncoding | 如果是正確的,則用區塊編碼編寫字段,以允許跳過未知的字段資料。這會影響效能。 | 錯誤的 |
chunkSize | 每個區塊的最大尺寸用於區塊編碼。 | 1024 |
TaggedFieldSerializer也繼承了FieldSerializer的所有設定。
CompatibleFieldSerializer擴展了FieldSerializer,以提供前向和向後的相容性。這意味著可以新增或刪除欄位而不會使先前序列化位元組的無效。不支援重命名或變更欄位類型。像fieldSerializer一樣,它可以在不需要註釋的情況下序列化大多數類別。
向前和向後的相容性和序列化效能取決於readUnknownFieldData
和chunkedEncoding
設定。此外,在序列化位元組中首次遇到該類別時,編寫了一個簡單的模式,其中包含欄位名稱字串。由於字段資料是透過名稱標識的,因此,如果超級類別具有與子類別相同名稱的字段,則extendedFieldNames
必須為真。
環境 | 描述 | 預設值 |
---|---|---|
readUnknownFieldData | 當遇到錯誤和未知欄位時,會拋出異常,或者,如果chunkedEncoding 為真,則跳過了資料。如果為true,則每個欄位值的類別是在值之前寫入的。遇到未知欄位時,嘗試讀取資料。這用於跳過數據,如果啟用了引用,則物件圖中的任何其他值仍然可以被序列化。如果讀取資料失敗(例如,類別是未知或已刪除的),則會引發異常,或者,如果 chunkedEncoding 為真,則跳過資料。無論哪種情況,如果跳過資料並啟用了引用,則不會讀取跳過資料中的任何引用,並且進一步的避免序列化可能會收到錯誤的參考和失敗。 | 真的 |
chunkedEncoding | 如果是正確的,則用區塊編碼編寫字段,以允許跳過未知的字段資料。這會影響效能。 | 錯誤的 |
chunkSize | 每個區塊的最大尺寸用於區塊編碼。 | 1024 |
相容於fieldserializer也繼承了字段序列化器的所有設定。
BeanSerializer與FieldSerializer非常相似,只是它使用Bean Getter和Setter方法而不是直接存取。這個稍慢,但可能更安全,因為它使用公共API來配置物件。像fieldSerializer一樣,它不提供前向或向後相容。
CollectionSerializer序列化實作Java.util.Collection介面的物件。
環境 | 描述 | 預設值 |
---|---|---|
elementsCanBeNull | 當錯誤時,假定集合中沒有元素為null,它可以節省每個元素的0-1位元組。 | 真的 |
elementClass | 設定用於集合中每個元素的混凝土類別。這消除了為每個元素編寫類別ID的需要。如果已知元素類別(例如,透過仿製藥)和原始的,原始的包裝器或最終,則即使在此設定為null的情況下,CollectionSerialializer也不會編寫類別ID。 | 無效的 |
elementSerializer | 設定用於集合中每個元素的序列化器。如果設定了序列化器,則某些序列化也需要設定值類別。如果為null,將使用在Kryo註冊的每個元素類別的序列化器。 | 無效的 |
映射器序列化實作java.util.map介面的物件。
環境 | 描述 | 預設值 |
---|---|---|
keysCanBeNull | 假設false時,地圖中沒有鍵為null,可以節省每個條目0-1位元組。 | 真的 |
valuesCanBeNull | 假設偽造時,地圖中沒有值是空的,可以節省每個條目的0-1位元組。 | 真的 |
keyClass | 設定用於地圖中每個鍵的混凝土類別。這消除了為每個鍵編寫類別ID的需要。 | 無效的 |
valueClass | 設定用於地圖中每個值的混凝土類別。這消除了為每個值編寫類別ID的需要。 | 無效的 |
keySerializer | 設定用於地圖中每個按鍵的序列化器。如果設定了值序列化器,則某些序列化也需要設定值類別。如果null,將使用在Kryo註冊的每個金鑰類別的序列化器。 | 無效的 |
valueSerializer | 設定用於地圖中每個值的序列化器。如果設定了密鑰序列化器,則某些序列化也需要設定值類別。如果為null,將使用在Kryo註冊的每個值的類別的序列化器。 | 無效的 |
Javaserializer和externalizableserializer是使用Java內建序列化的Kryo序列化器。這與往常一樣慢,但對於舊課程來說可能是必要的。
java.io.externizable和java.io.Serializable預設設定預設序列化器,因此必須手動設定預設序列化器,或在註冊類別時設定序列化序列化。
class SomeClass implements Externalizable { /* ... */ }
kryo . addDefaultSerializer ( Externalizable . class , ExternalizableSerializer . class );
kryo . register ( SomeClass . class );
kryo . register ( SomeClass . class , new JavaSerializer ());
kryo . register ( SomeClass . class , new ExternalizableSerializer ());
Kryo利用了低頂,輕巧的MinLog日誌記錄庫。可以透過以下方法之一設定記錄級:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo在INFO
(預設值)及以上等級上沒有記錄。 DEBUG
在開發過程中很方便。在調試特定問題時, TRACE
很容易使用,但是通常會輸出太多資訊以至於無法繼續使用。
MinLog支援固定的記錄級別,這會導致Java編譯器在編譯時刪除低於該級別的記錄語句。 Kryo必須用固定的記錄級MinLog Jar編譯。
Kryo不是線程的安全。每個執行緒應具有自己的Kryo,輸入和輸出實例。
由於Kryo不是執行緒的安全性,並且可以考慮在多執行緒環境中或池化中建置和配置Kryo實例相對昂貴。
static private final ThreadLocal < Kryo > kryos = new ThreadLocal < Kryo >() {
protected Kryo initialValue () {
Kryo kryo = new Kryo ();
// Configure the Kryo instance.
return kryo ;
};
};
Kryo kryo = 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 ();
// Configure the Kryo instance.
return kryo ;
}
};
Kryo kryo = kryoPool . obtain ();
// Use the Kryo instance here.
kryoPool . free ( kryo );
Pool < Output > outputPool = new Pool < Output >( true , false , 16 ) {
protected Output create () {
return new Output ( 1024 , - 1 );
}
};
Output output = outputPool . obtain ();
// Use the Output instance here.
outputPool . free ( output );
Pool < Input > inputPool = new Pool < Input >( true , false , 16 ) {
protected Input create () {
return new Input ( 1024 );
}
};
Input input = inputPool . obtain ();
// Use the Input instance here.
inputPool . free ( input );
如果將true
作為第一個參數傳遞給池建構函數,則池在內部使用同步,可以透過多個執行緒同時存取。
如果將true
作為第二個參數傳遞給池建構器,則池使用java.lang.ref.softreference儲存物件。這允許當JVM上的記憶體壓力高時,池中的物件是垃圾收集的。泳池clean
均取消所有已收集垃圾的軟參考。當未設定最大容量時,這可以減少池的大小。當游泳池具有最大容量時,不必打電話clean
因為如果達到了最大容量,則池free
將嘗試刪除空的參考。
第三個池參數是最大容量。如果釋放對象並且池已經包含最大數量的自由對象,則指定對像是重置但未添加到池中的。最大容量可以無限制省略。
如果reset
物件實作池。這使該物件有機會重置其未來重複使用狀態。另外,可以覆蓋池reset
以重置物件。輸入和輸出實作可池以設定其position
並total
為0。如果您透過setAutoReset(false)
停用自動重置,請確保在將實例傳回池之前呼叫Kryo.reset()
。
池getFree
傳回可獲得的物件數量。如果使用軟引用,則此數字可能包括已收集垃圾的物件。 clean
可先用於刪除空的軟引用。
Pool getPeak
傳回史上最高數量的免付費物件。這可以幫助確定池的最大容量是否適當設定。它可以隨時使用resetPeak
。
Kryo提供了許多基於JMH的基準和R/GGPLOT2文件。
可以將Kryo與JVM Serializers專案中的許多其他序列化庫進行比較。基準很小,過時和本土,而不是使用JMH,因此值得信賴。同樣,很難使用基準測試徹底比較序列化庫。圖書館具有許多不同的功能,並且通常具有不同的目標,因此他們可能會擅長解決完全不同的問題。為了了解這些基準,應分析正在運行的程式碼和序列化的資料與您的特定需求進行鮮明對比。有些序列化器是高度最佳化的,並使用程式碼的頁面,有些則僅使用幾行。這很好地展示了可能的東西,但對於許多情況來說可能不是相關的比較。
使用Kryo有許多項目。下面列出了一些。如果您想在此處包含您的項目,請提交拉動請求。