Kryo は、Java 用の高速かつ効率的なバイナリ オブジェクト グラフ シリアル化フレームワークです。プロジェクトの目標は、高速、低サイズ、そして使いやすい API です。このプロジェクトは、ファイル、データベース、ネットワーク上など、オブジェクトを永続化する必要がある場合に役立ちます。
Kryo は、自動のディープおよびシャロー コピー/クローン作成も実行できます。これは、オブジェクトからバイト、オブジェクトへではなく、オブジェクトからオブジェクトへの直接コピーです。
このドキュメントは Kryo バージョン 5.x 用です。バージョン 4.x については Wiki を参照してください。
質問、議論、サポートについては、Kryo メーリング リストをご利用ください。 Kryo 問題トラッカーの使用は、質問、ディスカッション、サポートではなく、バグと機能拡張に限定してください。
Kryo は 2 種類のアーティファクト/jar を公開しています。
Kryo JAR は、リリース ページおよび Maven Central で入手できます。 Kryo の最新のスナップショット (マスターのスナップショット ビルドを含む) は 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 を使用するには、lib にある依存関係 JAR とともに Kryo 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 クラスはシリアル化を自動的に実行します。 Output クラスと Input クラスは、バイトのバッファリングと、オプションでストリームへのフラッシュを処理します。
このドキュメントの残りの部分では、これがどのように機能するか、およびライブラリの高度な使用方法について詳しく説明します。
Kryo へのデータの入出力は、Input クラスと Output クラスを使用して行われます。これらのクラスはスレッドセーフではありません。
Output クラスは、バイト配列バッファにデータを書き込む OutputStream です。バイト配列が必要な場合は、このバッファを取得して直接使用できます。 Output に OutputStream が指定されている場合、バッファーがいっぱいになるとバイトがストリームにフラッシュされます。それ以外の場合、Output はバッファーを自動的に拡張できます。出力には、プリミティブと文字列をバイトに効率的に書き込むためのメソッドが多数あります。 DataOutputStream、BufferedOutputStream、FilterOutputStream、ByteArrayOutputStream と同様の機能をすべて 1 つのクラスで提供します。
ヒント: 出力と入力は、ByteArrayOutputStream のすべての機能を提供します。出力を ByteArrayOutputStream にフラッシュする理由はほとんどありません。
出力は、OutputStream への書き込み時にバイトをバッファリングするため、バッファリングされたバイトが OutputStream に書き込まれるようにするには、書き込み完了後にflush
またはclose
を呼び出す必要があります。出力に OutputStream が提供されていない場合、 flush
またはclose
呼び出す必要はありません。多くのストリームとは異なり、Output インスタンスは、位置を設定するか、新しいバイト配列またはストリームを設定することによって再利用できます。
ヒント: 出力はすでにバッファーされているため、出力を BufferedOutputStream にフラッシュする理由はありません。
引数がゼロの Output コンストラクターは、初期化されていない出力を作成します。出力setBuffer
出力を使用する前に呼び出す必要があります。
Input クラスは、バイト配列バッファからデータを読み取る InputStream です。バイト配列からの読み取りが必要な場合は、このバッファを直接設定できます。入力にInputStreamが指定されている場合、バッファ内のすべてのデータが読み取られたときに、ストリームからバッファにデータが入力されます。入力には、バイトからプリミティブと文字列を効率的に読み取るための多くのメソッドがあります。 DataInputStream、BufferedInputStream、FilterInputStream、ByteArrayInputStream と同様の機能をすべて 1 つのクラスで提供します。
ヒント: Input は ByteArrayInputStream のすべての機能を提供します。 ByteArrayInputStream から入力を読み取る理由はほとんどありません。
Input close
が呼び出されると、Input の InputStream (存在する場合) が閉じられます。 InputStream から読み取らない場合は、 close
呼び出す必要はありません。多くのストリームとは異なり、Input インスタンスは、位置と制限を設定するか、新しいバイト配列または InputStream を設定することによって再利用できます。
引数がゼロの入力コンストラクターは、初期化されていない入力を作成します。入力setBuffer
入力を使用する前に呼び出す必要があります。
ByteBufferOutput クラスと ByteBufferInput クラスは、バイト配列ではなく ByteBuffer を使用することを除いて、Output クラスと Input クラスとまったく同じように機能します。
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 は 1 バイト、128 ~ 16383 は 2 バイトなどに書き込まれます。ただし、小さな負の数は最悪のケースで 5 バイトになります。正の値に最適化されていない場合、これらの範囲は半分だけ下にシフトされます。たとえば、-64 ~ 63 は 1 バイトで、64 ~ 8191 と -65 ~ -8192 は 2 バイトで書き込まれます。
入力バッファと出力バッファは、固定サイズまたは可変長の値を読み書きするメソッドを提供します。固定サイズの値を書き込むか可変長の値を書き込むかをバッファーに決定させる方法もあります。これにより、シリアル化コードは、固定サイズが使用されると出力が肥大化する非常に一般的な値に対して可変長エンコーディングが使用されることを保証しながら、他のすべての値についてはバッファー構成が決定できるようにします。
方法 | 説明 |
---|---|
writeInt(int) | 4バイトのintを書き込みます。 |
writeVarInt(int, ブール値) | 1 ~ 5 バイトの int を書き込みます。 |
writeInt(int, ブール値) | 4 バイトまたは 1 ~ 5 バイトの int を書き込みます (バッファによって決定されます)。 |
writeLong(ロング) | 8 バイトの長さを書き込みます。 |
writeVarLong(ロング、ブール値) | 1 ~ 9 バイトの長さを書き込みます。 |
writeLong(ロング、ブール値) | 8 バイトまたは 1 ~ 9 バイトの長さを書き込みます (バッファによって決定されます)。 |
すべての値の可変長エンコーディングを無効にするには、 writeVarInt
、 writeVarLong
、 readVarInt
、およびreadVarLong
メソッドをオーバーライドする必要があります。
いくつかのデータの長さを書き込んでから、データを書き込むと便利な場合があります。データの長さが事前にわからない場合は、すべてのデータをバッファリングして長さを決定する必要があり、その後、長さを書き込んでからデータを書き込みます。これに単一の大きなバッファを使用すると、ストリーミングが妨げられ、不当に大きなバッファが必要になる可能性があり、これは理想的ではありません。
チャンクエンコーディングは、小さなバッファを使用することでこの問題を解決します。バッファがいっぱいになると、その長さが書き込まれ、次にデータが書き込まれます。これがデータの 1 つのチャンクです。バッファはクリアされ、書き込むデータがなくなるまでこれが続きます。長さがゼロのチャンクは、チャンクの終わりを示します。
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 が使用されます。これは入力を拡張するため、データを読み取るための便利なメソッドがすべて備わっています。読み取り時、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 である必要がある場合は許容できる可能性があります。
可変長エンコードは、特に大量のデータを使用する場合、固定値よりも遅くなります。
チャンクエンコーディングでは中間バッファーが使用されるため、すべてのバイトのコピーが 1 つ追加されます。これだけでも許容できる場合がありますが、再入可能シリアライザーで使用する場合、シリアライザーはオブジェクトごとに OutputChunked または InputChunked を作成する必要があります。シリアル化中にこれらのバッファーを割り当ててガベージ コレクションを行うと、パフォーマンスに悪影響を及ぼす可能性があります。
Kryo には、オブジェクトの読み取りと書き込みのための 3 セットのメソッドがあります。オブジェクトの具象クラスが不明で、オブジェクトが null である可能性がある場合:
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 );
クラスが既知であり、オブジェクトを null にすることができない場合:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
これらのメソッドはすべて、まず使用する適切なシリアライザーを見つけてから、それを使用してオブジェクトをシリアル化または逆シリアル化します。シリアライザーは、再帰的シリアル化のためにこれらのメソッドを呼び出すことができます。同じオブジェクトへの複数の参照と循環参照は、Kryo によって自動的に処理されます。
オブジェクトの読み取りと書き込みのメソッドに加えて、Kryo クラスは、シリアライザーの登録、クラス識別子の効率的な読み取りと書き込み、null を受け入れることができないシリアライザーの null オブジェクトの処理、およびオブジェクト参照の読み取りと書き込みの処理 (有効な場合) を行う方法を提供します。これにより、シリアライザーはシリアル化タスクに集中できるようになります。
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 バイトの容量を持つバッファーから始まります。より多くのバイトが出力に書き込まれると、バッファのサイズは無制限に増加します。 OutputStream が与えられていないため、Output を閉じる必要はありません。入力は出力の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
で有効または無効になります。
参照が有効になっている場合、オブジェクト グラフに初めて表示されるときに、各オブジェクトの前にバリアントが書き込まれます。同じオブジェクト グラフ内でそのクラスがその後出現する場合、バリアントのみが書き込まれます。逆シリアル化後、循環参照を含むオブジェクト参照が復元されます。使用中のシリアライザーは、 Serializer read
で Kryo reference
呼び出して参照をサポートする必要があります。
参照を有効にすると、読み取りまたは書き込みされるすべてのオブジェクトを追跡する必要があるため、パフォーマンスに影響します。
内部では、ReferenceResolver が読み取りまたは書き込みされたオブジェクトの追跡を処理し、int 参照 ID を提供します。複数の実装が提供されています。
ReferenceResolver useReferences(Class)
オーバーライドできます。クラスに対して参照がサポートされているかどうかを決定するブール値を返します。クラスが参照をサポートしていない場合、可変長参照 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
も同様ですが、各オブジェクト グラフがシリアル化または逆シリアル化された後にクリアされます。これにより、現在のオブジェクト グラフにのみ関連する状態の管理が容易になります。たとえば、これを使用して、オブジェクト グラフでクラスが初めて検出されたときにスキーマ データを書き込むことができます。例については、「SupportedFieldSerializer」を参照してください。
デフォルトでは、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 が割り当てられます。これは、クラスが登録される順序が重要であることを意味します。順序を重要視しないように、オプションでクラス 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 はデフォルトでプリミティブ型と String に使用されますが、これらの ID は再利用できます。 ID は正の最適化されたバリアントとして書き込まれるため、小さな正の整数である場合に最も効率的です。ネガティブ ID は効率的にシリアル化されません。
内部では、ClassResolver がクラスを表すバイトの実際の読み取りと書き込みを処理します。ほとんどの場合、デフォルトの実装で十分ですが、クラスが登録されたときの動作、シリアル化中に未登録のクラスが見つかったときの動作、クラスを表すために読み書きされる内容をカスタマイズするために置き換えることができます。
Kryo は、事前にクラスを登録せずにシリアル化を許可するように構成できます。
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
登録済みクラスと未登録クラスを混在して使用できます。未登録クラスには 2 つの大きな欠点があります。
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 のデフォルトのシリアライザーの 1 つを選択する代わりに使用されます。
@ 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 は、ReflectASM を使用してオブジェクトを作成し、引数なしのコンストラクターを呼び出す DefaultInstantiatorStrategy を提供します。それが不可能な場合は、リフレクションを使用して引数なしのコンストラクターを呼び出します。これも失敗した場合は、例外をスローするか、フォールバックの InstantiatorStrategy を試行します。 Reflection はsetAccessible
を使用するため、引数なしのプライベート コンストラクターは、Kryo がパブリック API に影響を与えることなくクラスのインスタンスを作成できるようにする良い方法となります。
DefaultInstantiatorStrategy は、Kryo でオブジェクトを作成する場合に推奨される方法です。 Java コードの場合と同じようにコンストラクターを実行します。別の言語外のメカニズムを使用してオブジェクトを作成することもできます。 Objenesis StdInstantiatorStrategy は、JVM 固有の API を使用して、コンストラクターをまったく呼び出すことなくクラスのインスタンスを作成します。ほとんどのクラスはコンストラクターが呼び出されることを期待しているため、これを使用するのは危険です。コンストラクターをバイパスしてオブジェクトを作成すると、オブジェクトが初期化されていない状態または無効な状態のままになる可能性があります。クラスはこのように作成されるように設計する必要があります。
Kryo は、最初に DefaultInstantiatorStrategy を試行し、必要に応じて StdInstantiatorStrategy にフォールバックするように構成できます。
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
もう 1 つのオプションは 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 );
}
});
一部のシリアライザーは、 create
時に必要なデータを適切なタイミングで書き込むためにオーバーライドできるwriteHeader
メソッドを提供します。
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
提供しない場合、 create
のデータの書き込みはwrite
で実行できます。
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 ());
}
}
シリアライザーが値の予期されるクラス (フィールドのクラスなど) を知っている場合でも、値の具体的なクラスが Final でない場合、シリアライザーは最初にクラス ID を書き込み、次に値を書き込む必要があります。最終クラスは非ポリモーフィックであるため、より効率的にシリアル化できます。
Kryo isFinal
は、クラスが最終かどうかを判断するために使用されます。このメソッドは、最終型でない場合でも true を返すようにオーバーライドできます。たとえば、アプリケーションが ArrayList を広範囲に使用するが、ArrayList サブクラスをまったく使用しない場合、ArrayList を最終的なものとして扱うことで、FieldSerializer が ArrayList フィールドごとに 1 ~ 2 バイトを節約できる可能性があります。
Kryo は、java.io.Serializable を実装する Java 8 以降のクロージャをシリアル化できますが、いくつかの注意点があります。 1 つの 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 );
Serializable を実装していないクロージャをシリアル化することは、ある程度の努力で可能です。
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 ());
}
}
シリアライザーに実装する必要があるメソッドは 2 つだけです。 write
オブジェクトをバイトとして出力に書き込みます。 read
オブジェクトの新しいインスタンスを作成し、入力から読み取ってそれに値を設定します。
Kryo を使用してシリアライザー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 はシリアル化を調整し、参照や null オブジェクトなどの機能を処理できるようになります。シリアライザーは、ネストされたオブジェクトにどのシリアライザーを使用するかを知っている場合があります。その場合、シリアライザーを受け入れる Kryo の読み取りおよび書き込みメソッドを使用する必要があります。
オブジェクトが null の可能性がある場合:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
オブジェクトを null にできない場合:
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 を処理することで効率が向上する場合は、シリアライザーsetAcceptsNull(true)
を呼び出すことができます。これは、シリアライザーが処理するすべてのインスタンスが決して null にならないことがわかっている場合に、null を示すバイトの書き込みを避けるためにも使用できます。
Kryo getGenerics
ジェネリック型情報を提供するため、シリアライザーの効率が向上します。これは、型パラメーター クラスが Final である場合にクラスの書き込みを避けるために最も一般的に使用されます。
ジェネリック型推論はデフォルトで有効になっており、 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 が返された場合は、その後に Generics 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 ;
}
}
シリアライザーを使用する代わりに、クラスは KryoSerializable (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
呼び出す前にインスタンスが作成されている必要があるため、クラスは自身の作成を制御できません。 KryoSerializable クラスは、デフォルトのシリアライザー KryoSerializableSerializer を使用します。これは、Kryo newInstance
を使用して新しいインスタンスを作成します。独自のシリアライザーを作成してプロセスをカスタマイズしたり、シリアル化の前後にメソッドを呼び出したりすることは簡単です。
シリアライザーは、 copy
がオーバーライドされる場合にのみコピーをサポートします。 Serializer read
と同様に、このメソッドにはコピーを作成および構成するロジックが含まれています。 read
と同様に、子オブジェクトのいずれかが親オブジェクトを参照できる場合は、Kryo を使用して子オブジェクトをコピーする前に、Kryo reference
を呼び出す必要があります。
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がDaserializationに使用されると仮定しているため、記述されている形式を明示的に定義していません。シリアイザーは、他の言語でより簡単に読み取る標準化された形式を使用して記述できますが、これはデフォルトでは提供されません。
シリアル化されたバイトの長期保存など、いくつかのニーズについては、シリアル化がクラスの変化を処理する方法が重要です。これは、フォワード互換性(新しいクラスによってシリアル化されたバイトの読み取り)および後方互換性(古いクラスによってシリアル化されたバイトの読み取り)として知られています。 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は、さまざまな構成オプションと互換性のレベルを多くのシリアル化担当者に提供します。追加のシリアーザーは、プライベートAPIにアクセスするか、すべてのJVMで完全に安全ではないシリアル剤をホストするKryo-Serializers Sister Projectにあります。リンクセクションには、より多くのシリアル化剤があります。
FieldSerializerは、各非転移フィールドをシリアル化することにより機能します。 Pojosや他の多くのクラスを構成なしでシリアル化できます。すべての非公開フィールドはデフォルトで書かれて読み取られているため、シリアル化される各クラスを評価することが重要です。フィールドが公開されている場合、シリアル化はより高速になる可能性があります。
FieldSerializerは、SchemaとしてJavaクラスファイルを使用して、スキーマ情報なしでフィールドデータのみを記述することにより、効率的です。以前にシリアル化されたバイトを無効にすることなく、フィールドのタイプの追加、削除、または変更をサポートしません。フィールドの変更は、フィールドのアルファベット順に変更されない場合にのみ許可されます。
FieldSerializerの互換性の欠点は、ネットワークを介してデータを送信する場合など、多くの状況で受け入れられる可能性がありますが、Javaクラスが進化できないため、長期データストレージには適していない場合があります。多くの場合、タグ付きFieldSerializerがより良い選択です。
設定 | 説明 | デフォルト値 |
---|---|---|
fieldsCanBeNull | 偽の場合、フィールド値はnullであり、フィールドごとに0-1バイトを節約できると想定されています。 | 真実 |
setFieldsAsAccessible | 真実の場合、すべての非転換フィールド(プライベートフィールドを含む)は、必要に応じてシリアル化され、 setAccessible なります。 FALSEの場合、公開APIのフィールドのみがシリアル化されます。 | 真実 |
ignoreSyntheticFields | trueの場合、合成フィールド(スコーピングのためにコンパイラによって生成)がシリアル化されます。 | 間違い |
fixedFieldTypes | 真実の場合、すべてのフィールド値のコンクリートタイプがフィールドのタイプと一致すると想定されています。これにより、フィールド値のクラスIDを書き込む必要があります。 | 間違い |
copyTransient | Trueの場合、すべての過渡フィールドがコピーされます。 | 真実 |
serializeTransient | Trueの場合、一時的なフィールドはシリアル化されます。 | 間違い |
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の場合、フィールド値は決してnullであり、0-1バイトを節約できます。 | 真実 |
valueClass | フィールド値に使用するコンクリートクラスとシリアイザーを設定します。これにより、値に対してクラスIDを記述する必要性が削除されます。 Field Valueのクラスが原始的で原始的なラッパー、または最終的なものである場合、この設定はフィールドのクラスにデフォルトです。 | ヌル |
serializer | フィールド値に使用するシリアイザーを設定します。シリアイザーが設定されている場合、一部のシリアイザーは、値クラスも設定する必要がありました。 NULLの場合、Field ValueのクラスのためにKryoに登録されたSerializerが使用されます。 | ヌル |
variableLengthEncoding | Trueの場合、変数の長さ値が使用されます。これは、INTまたは長いフィールドにのみ適用されます。 | 真実 |
optimizePositive | TRUEの場合、正の値は変数の長さ値に対して最適化されます。これは、可変長さエンコードが使用される場合にのみINTまたは長いフィールドに適用されます。 | 真実 |
注釈は、各フィールドのシリアイザーを構成するために使用できます。
注釈 | 説明 |
---|---|
@Bind | 任意のフィールドにキャッシュフィールド設定を設定します。 |
@CollectionBind | コレクションフィールドのコレクションセリアライザー設定を設定します。 |
@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 ;
}
VersionFieldSerializerは、FieldSerializerを拡張し、後方互換性を提供します。これは、以前にシリアル化されたバイトを無効にすることなく、フィールドを追加できることを意味します。フィールドのタイプの削除、変更、または変更はサポートされていません。
フィールドが追加された場合、以前にシリアル化されたバイトと互換性があるために追加されたバージョンを示すために、 @Since(int)
アノテーションが必要です。注釈値は決して変更してはなりません。
VersionFieldSerializerは、フィールドセリアライザーにオーバーヘッドをほとんど追加しません。単一の追加のVarint。
設定 | 説明 | デフォルト値 |
---|---|---|
compatible | falseの場合、異なるバージョンでオブジェクトを読み取るときに例外がスローされます。オブジェクトのバージョンは、任意のフィールドの最大バージョンです。 | 真実 |
VersionFieldSerializerは、FieldSerializerのすべての設定も継承します。
TaggedFieldSerializerは、FieldSerializerを拡張して、後方互換性とオプションのフォワード互換性を提供します。これは、フィールドを追加または変更して、以前にシリアル化されたバイトを無効にすることなく、オプションで削除できることを意味します。フィールドのタイプを変更することはサポートされていません。
@Tag(int)
アノテーションを持つフィールドのみがシリアル化されます。フィールドタグ値は、クラス内とそのすべてのスーパークラスの両方で一意でなければなりません。重複したタグ値が発生した場合、例外がスローされます。
前方および後方の互換性とシリアル化のパフォーマンスは、 readUnknownTagData
およびchunkedEncoding
設定に依存します。さらに、VARINTは、タグ値の各フィールドの前に記述されます。
readUnknownTagData
とchunkedEncoding
がfalseである場合、フィールドを削除する必要はありませんが、 @Deprecated
Annotationを適用できます。非推奨フィールドは、古いバイトを読むときに読まれますが、新しいバイトには書かれていません。クラスは、非推奨フィールドの価値を読み、他の場所に書き込むことで進化することができます。フィールドは、クラスの混乱を減らすために名前を変更および/またはプライベートにすることができます(たとえば、 ignored1
、 ignored2
)。
TaggedFieldSerializer( readUnknownTagData
およびchunkedEncoding
Falseを使用)は、フィールドに注釈を付けることができるほとんどのクラスの提案されたシリアイザーです。これにより、クラスが進化し、シリアル化されたデータから(DepRecationによる)フィールドを削除し、シリアル化されたサイズに多くを追加せずに、ほとんどのアプリケーションのニーズを満たすことができます。
設定 | 説明 | デフォルト値 |
---|---|---|
readUnknownTagData | falseと未知のタグに遭遇した場合、例外がスローされるか、 chunkedEncoding が真である場合、データがスキップされます。Trueの場合、各フィールド値のクラスは値の前に記述されます。未知のタグに遭遇したとき、データを読み取ろうとする試みが行われます。これは、データのスキップに使用され、参照が有効になっている場合、データを依然として脱必要にすることができるオブジェクトグラフの他の値があります。データの読み取りが失敗した場合(例:クラスが不明または削除されている場合)、例外がスローされるか、 chunkedEncoding が真である場合、データはスキップされます。どちらの場合でも、データがスキップされ、参照が有効になっている場合、スキップされたデータの参照は読み取られず、さらに脱出は間違った参照を受け取り、失敗する可能性があります。 | 間違い |
chunkedEncoding | Trueの場合、フィールドは、不明なフィールドデータをスキップできるように、チャンクエンコードで記述されます。これはパフォーマンスに影響します。 | 間違い |
chunkSize | チャンクされたエンコードの各チャンクの最大サイズ。 | 1024 |
TaggedFieldSerializerは、FieldSerializerのすべての設定も継承します。
互換性のあるFieldSerializerは、FieldSerializerを拡張して、前方と後方の両方の互換性を提供します。これは、以前にシリアル化されたバイトを無効にすることなく、フィールドを追加または削除できることを意味します。フィールドのタイプの変更または変更はサポートされていません。 FieldSerializerのように、注釈を必要とせずにほとんどのクラスをシリアル化できます。
順方向および後方互換性とシリアル化パフォーマンスは、 readUnknownFieldData
とchunkedEncoding
設定に依存します。さらに、クラスがシリアル化されたバイトで初めて遭遇したとき、フィールド名の文字列を含む簡単なスキーマが書かれています。フィールドデータは名前で識別されるため、スーパークラスにサブクラスと同じ名前のフィールドがある場合、 extendedFieldNames
真でなければなりません。
設定 | 説明 | デフォルト値 |
---|---|---|
readUnknownFieldData | falseと未知のフィールドに遭遇した場合、例外がスローされるか、 chunkedEncoding が真である場合、データがスキップされます。Trueの場合、各フィールド値のクラスは値の前に記述されます。未知のフィールドに遭遇すると、データを読み取ろうとする試みが行われます。これは、データのスキップに使用され、参照が有効になっている場合、データを依然として脱必要にすることができるオブジェクトグラフの他の値があります。データの読み取りが失敗した場合(クラスが不明または削除されていない場合)、例外がスローされるか、 chunkedEncoding が真である場合、データはスキップされます。どちらの場合でも、データがスキップされ、参照が有効になっている場合、スキップされたデータの参照は読み取られず、さらに脱出は間違った参照を受け取り、失敗する可能性があります。 | 真実 |
chunkedEncoding | Trueの場合、フィールドは、不明なフィールドデータをスキップできるように、チャンクエンコードで記述されます。これはパフォーマンスに影響します。 | 間違い |
chunkSize | チャンクされたエンコードの各チャンクの最大サイズ。 | 1024 |
互換性のあるFieldSerializerは、FieldSerializerのすべての設定も継承します。
Beanserializerは、直接的なフィールドアクセスではなく、Bean GetterおよびSetterメソッドを使用していることを除いて、FieldSerializerに非常に似ています。これはわずかに遅くなりますが、パブリックAPIを使用してオブジェクトを構成するため、より安全な場合があります。 FieldSerializerのように、それは前方または後方の互換性を提供しません。
collectionserializer java.util.collectionインターフェイスを実装するオブジェクトをシリアル化します。
設定 | 説明 | デフォルト値 |
---|---|---|
elementsCanBeNull | 偽の場合、コレクション内の要素がnullであり、要素ごとに0-1バイトを節約できると想定されています。 | 真実 |
elementClass | コレクション内の各要素に使用するコンクリートクラスを設定します。これにより、各要素のクラスIDを書き込む必要があります。要素クラスが既知(ジェネリックなど)と原始的で原始的なラッパー、または最終的な場合、CollectionSerializerは、この設定がnullであってもクラスIDを書きません。 | ヌル |
elementSerializer | コレクション内のすべての要素に使用するシリアイザーを設定します。シリアイザーが設定されている場合、一部のシリアイザーは、値クラスも設定する必要がありました。 nullの場合、各要素のクラスについてKryoに登録されたシリアナーが使用されます。 | ヌル |
Mapserializerは、java.util.mapインターフェイスを実装するオブジェクトをシリアル化します。
設定 | 説明 | デフォルト値 |
---|---|---|
keysCanBeNull | FALSEの場合、マップ内のキーがnullでないと想定されており、エントリごとに0-1バイトを節約できます。 | 真実 |
valuesCanBeNull | 偽の場合、マップ内の値はnullであり、エントリごとに0-1バイトを節約できると想定されています。 | 真実 |
keyClass | マップ内のすべてのキーに使用するコンクリートクラスを設定します。これにより、各キーのクラスIDを書き込む必要があります。 | ヌル |
valueClass | マップ内のすべての値に使用するコンクリートクラスを設定します。これにより、各値のクラスIDを書き込む必要があります。 | ヌル |
keySerializer | マップ内のすべてのキーに使用するシリアナーを設定します。値シリアイザーが設定されている場合、一部のシリアル化剤は、値クラスも設定する必要がありました。 NULLの場合、各キーのクラスについてKryoに登録されたシリアナーが使用されます。 | ヌル |
valueSerializer | マップ内のすべての値に使用するシリアイザーを設定します。キーシリアイザーが設定されている場合、一部のシリアイザーは、値クラスを設定する必要がありました。 nullの場合、各値のクラスについてKryoに登録されたシリアナーが使用されます。 | ヌル |
Javaserializerと外部化物造影剤は、Javaの組み込みのシリアル化を使用するKryo Serializersです。これは通常と同じくらい遅いJavaシリアル化ですが、レガシークラスには必要になる場合があります。
java.io.externalizableおよび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インスタンスの構築と構成は比較的高価であるため、マルチスレッド環境では、糸局所またはプーリングが考慮される可能性があります。
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
が渡された場合、プールは内部的に同期を使用し、複数のスレッドで同時にアクセスできます。
プールコンストラクターの2番目の引数としてtrue
が渡された場合、プールはjava.lang.ref.softreferenceを使用してオブジェクトを保存します。これにより、JVMに対するメモリ圧力が高いときに、プール内のオブジェクトが集められることがあります。プールclean
オブジェクトがゴミ収集されたすべてのソフト参照を削除します。これにより、最大容量が設定されていないときにプールのサイズを縮小できます。プールに最大容量がある場合、プールfree
最大容量に達した場合に空の参照を削除しようとするため、 clean
を呼び出す必要はありません。
3番目のプールパラメーターは最大容量です。オブジェクトが解放され、プールに最大数の自由オブジェクトが既に含まれている場合、指定されたオブジェクトはリセットされますが、プールに追加されません。最大容量は制限なしで省略できます。
オブジェクトがプールを実装する場合、プール可能な場合、オブジェクトが解放されたときにプーラブルreset
が呼び出されます。これにより、オブジェクトは将来の再利用のために状態をリセットする機会を与えます。または、プールのreset
をオーバーライドしてオブジェクトをリセットできます。入力と出力実装はプーラブルにposition
を設定し、 total
0に設定します。Kryoは、オブジェクトグラフ状態が通常、シリアル化後に自動的にリセットされるため、プーラブルを実装できません(リセットを参照)。 setAutoReset(false)
を介して自動リセットを無効にする場合は、プールにインスタンスを返す前にKryo.reset()
に電話してください。
Pool getFree
取得できるオブジェクトの数を返します。ソフト参照を使用している場合、この番号には、ゴミ収集されたオブジェクトが含まれる場合があります。最初にclean
使用して、空のソフト参照を削除することができます。
Pool getPeak
史上最高の数の無料オブジェクトを返します。これは、プールの最大容量が適切に設定されているかどうかを判断するのに役立ちます。 resetPeak
でいつでもリセットできます。
Kryoは、JMHベースの多くのベンチマークとR/GGPLOT2ファイルを提供しています。
Kryoは、JVM Serializersプロジェクトの他の多くのシリアル化ライブラリと比較できます。ベンチマークは、JMHを使用するのではなく、小さく、日付が付けられ、自家製のものであるため、あまり信頼できません。また、ベンチマークを使用してシリアル化ライブラリを徹底的に比較することは非常に困難です。ライブラリにはさまざまな機能があり、多くの場合異なる目標があるため、まったく異なる問題を解決することに優れている可能性があります。これらのベンチマークを理解するには、実行中のコードとシリアル化されているデータを分析し、特定のニーズと対照的にする必要があります。いくつかのシリアル化剤は高度に最適化されており、コードのページを使用し、他のシリアルは数行しか使用していません。これは、何が可能かを示すのは良いことですが、多くの状況に関連する比較ではないかもしれません。
Kryoを使用するプロジェクトは多数あります。以下にリストされています。ここにプロジェクトが必要な場合は、プルリクエストを送信してください。