1. 概要

Kryo は、速度、効率、およびユーザーフレンドリーなAPIに重点を置いたJavaシリアル化フレームワークです。

この記事では、Kryoフレームワークの主要な機能を調べ、その機能を紹介する例を実装します。

2. Mavenの依存関係

最初に行う必要があるのは、kryo依存関係をpom.xmlに追加することです。

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

このアーティファクトの最新バージョンは、 MavenCentralにあります。

3. クリオの基本

まず、Kryoがどのように機能し、Kryoを使用してオブジェクトをシリアル化および逆シリアル化する方法を見ていきましょう。

3.1. 序章

フレームワークは、すべての機能のメインエントリポイントとしてKryoクラスを提供します。

このクラスは、シリアル化プロセスを調整し、クラスを Serializer インスタンスにマップします。このインスタンスは、オブジェクトのグラフをバイト表現に変換する詳細を処理します。

バイトの準備ができたら、Outputオブジェクトを使用してストリームに書き込まれます。 このようにして、ファイルやデータベースに保存したり、ネットワーク経由で送信したりできます。

後でオブジェクトが必要になると、 Input インスタンスを使用してそれらのバイトを読み取り、Javaオブジェクトにデコードします。

3.2. オブジェクトのシリアル化

例に飛び込む前に、まず、この記事の各テストケースで使用するいくつかの変数を初期化するユーティリティメソッドを作成しましょう。

@Before
public void init() {
    kryo = new Kryo();
    output = new Output(new FileOutputStream("file.dat"));
    input = new Input(new FileInputStream("file.dat"));
}

これで、Kryoを使用してオブジェクトを読み書きするのがいかに簡単であるかを見ることができます。

@Test
public void givenObject_whenSerializing_thenReadCorrectly() {
    Object someObject = "Some string";

    kryo.writeClassAndObject(output, someObject);
    output.close();

    Object theObject = kryo.readClassAndObject(input);
    input.close();

    assertEquals(theObject, "Some string");
}

close()メソッドの呼び出しに注意してください。 これが必要なのは、OutputクラスとInputクラスがそれぞれOutputStreamInputStreamから継承するためです。

複数のオブジェクトのシリアル化も同様に簡単です。

@Test
public void givenObjects_whenSerializing_thenReadCorrectly() {
    String someString = "Multiple Objects";
    Date someDate = new Date(915170400000L);

    kryo.writeObject(output, someString);
    kryo.writeObject(output, someDate);
    output.close();

    String readString = kryo.readObject(input, String.class);
    Date readDate = kryo.readObject(input, Date.class);
    input.close();

    assertEquals(readString, "Multiple Objects");
    assertEquals(readDate.getTime(), 915170400000L);
}

適切なクラスをreadObject()メソッドに渡していることに注意してください。これにより、コードがキャストフリーになります。

4. シリアライザー

このセクションでは、どのシリアライザーがすでに使用可能であるかを示してから、独自のシリアライザーを作成します。

4.1. デフォルトのシリアライザー

Kryoがオブジェクトをシリアル化すると、以前に登録された Serializer クラスのインスタンスが作成され、バイトへの変換が行われます。 これらはデフォルトのシリアライザーと呼ばれ、セットアップなしで使用できます。

ライブラリには、プリミティブ、リスト、マップ、列挙などを処理するそのようなシリアライザーがすでにいくつか用意されています。 特定のクラスのシリアライザーが見つからない場合は、 FieldSerializer が使用されます。これは、ほとんどすべてのタイプのオブジェクトを処理できます。

これがどのように見えるか見てみましょう。 まず、Personクラスを作成しましょう。

public class Person {
    private String name = "John Doe";
    private int age = 18;
    private Date birthDate = new Date(933191282821L);

    // standard constructors, getters, and setters
}

それでは、このクラスからオブジェクトを作成して、それを読み戻しましょう。

@Test
public void givenPerson_whenSerializing_thenReadCorrectly() {
    Person person = new Person();

    kryo.writeObject(output, person);
    output.close();

    Person readPerson = kryo.readObject(input, Person.class);
    input.close();

    assertEquals(readPerson.getName(), "John Doe");
}

FieldSerializer が自動的に作成されるため、Personオブジェクトをシリアル化するために何も指定する必要がないことに注意してください。

4.2. カスタムシリアライザー

シリアル化プロセスをさらに制御する必要がある場合は、2つのオプションがあります。 独自のSerializerクラスを作成して、Kryoに登録するか、クラスにシリアル化を単独で処理させることができます。

最初のオプションを示すために、Serializerを拡張するクラスを作成しましょう。

public class PersonSerializer extends Serializer<Person> {

    public void write(Kryo kryo, Output output, Person object) {
        output.writeString(object.getName());
        output.writeLong(object.getBirthDate().getTime());
    }

    public Person read(Kryo kryo, Input input, Class<Person> type) {
        Person person = new Person();
        person.setName(input.readString());
        long birthDate = input.readLong();
        person.setBirthDate(new Date(birthDate));
        person.setAge(calculateAge(birthDate));
        return person;
    }

    private int calculateAge(long birthDate) {
        // Some custom logic
        return 18;
    }
}

それでは、テストしてみましょう。

@Test
public void givenPerson_whenUsingCustomSerializer_thenReadCorrectly() {
    Person person = new Person();
    person.setAge(0);
    
    kryo.register(Person.class, new PersonSerializer());
    kryo.writeObject(output, person);
    output.close();

    Person readPerson = kryo.readObject(input, Person.class);
    input.close();

    assertEquals(readPerson.getName(), "John Doe");
    assertEquals(readPerson.getAge(), 18);
}

以前に0に設定したにもかかわらず、ageフィールドが18に等しいことに注意してください。

@DefaultSerializer アノテーションを使用して、Personオブジェクトを処理する必要があるたびにPersonSerializerを使用することをKryoに通知することもできます。 これにより、 register()メソッドの呼び出しを回避できます。

@DefaultSerializer(PersonSerializer.class)
public class Person implements KryoSerializable {
    // ...
}

2番目のオプションでは、 Person クラスを変更して、KryoSerializableインターフェイスを拡張します。

public class Person implements KryoSerializable {
    // ...

    public void write(Kryo kryo, Output output) {
        output.writeString(name);
        // ...
    }

    public void read(Kryo kryo, Input input) {
        name = input.readString();
        // ...
    }
}

このオプションのテストケースは前のテストケースと同じであるため、ここには含まれていません。 ただし、この記事のソースコードで見つけることができます。

4.3. Javaシリアライザー

散発的なケースでは、Kryoはクラスをシリアル化できません。 これが発生し、カスタムシリアライザーを作成するオプションがない場合は、 JavaSerializerを使用して標準のJavaシリアル化メカニズムを使用できます。 これには、クラスが通常どおりSerializableインターフェイスを実装する必要があります。

前述のシリアライザーを使用する例を次に示します。

public class ComplexObject implements Serializable {
    private String name = "Bael";
    
    // standard getters and setters
}
@Test
public void givenJavaSerializable_whenSerializing_thenReadCorrectly() {
    ComplexClass complexObject = new ComplexClass();
    kryo.register(ComplexClass.class, new JavaSerializer());

    kryo.writeObject(output, complexObject);
    output.close();

    ComplexClass readComplexObject = kryo.readObject(input, ComplexClass.class);
    input.close();

    assertEquals(readComplexObject.getName(), "Bael");
}

5. 結論

このチュートリアルでは、Kryoライブラリの最も注目すべき機能について説明しました。

複数の単純なオブジェクトをシリアル化し、FieldSerializerクラスを使用してカスタムオブジェクトを処理しました。 また、カスタムシリアライザーを作成し、必要に応じて標準のJavaシリアル化メカニズムにフォールバックする方法を示しました。

いつものように、この記事の完全なソースコードは、Githubにあります。