Kryo

シリアライゼーションJavaバイナリシリアライゼーション高速効率的オブジェクトグラフ

シリアライゼーションライブラリ

Kryo

概要

Kryoは、Java向けの高速で効率的なバイナリオブジェクトグラフシリアライゼーションフレームワークです。高速な処理速度、小さいサイズ、使いやすいAPIを目標としており、オブジェクトをファイル、データベース、ネットワーク経由で永続化する必要がある場合に有用です。

詳細

Kryoは、標準のJavaシリアライゼーションと比較して大幅に高いパフォーマンスを提供し、より小さなシリアライズ済みデータを生成します。自動的なディープコピーとシャローコピーをサポートし、複数の参照や循環参照を適切に処理できます。

主な特徴

  • 高速処理: 標準Javaシリアライゼーションより大幅に高速
  • コンパクトな出力: シリアライズされたデータサイズが小さい
  • 自動シリアライゼーション: プリミティブ、リスト、マップ、Enumなどのデフォルトシリアライザーを提供
  • カスタムシリアライザー: 特定のクラスに対してカスタムシリアライゼーションプロセスを定義可能
  • 複雑なオブジェクトグラフ対応: 循環参照やネストされたオブジェクトを適切に処理
  • スレッドセーフでない: 高パフォーマンスのためスレッドセーフではない設計
  • プラガブルアーキテクチャ: 異なるシリアライザーを柔軟に組み込み可能

使用されている環境

  • ビッグデータ処理: Apache Sparkなどのフレームワークで分散ノード間のデータシリアライゼーション
  • ネットワーク通信: 高速なデータ転送が必要なアプリケーション
  • キャッシング: キャッシュソリューションでの高速なオブジェクトシリアライゼーション
  • 永続ストレージ: 大量のデータを効率的に保存するアプリケーション

メリット・デメリット

メリット

  • 高速な処理速度: 標準Javaシリアライゼーションと比較して50%以上高速
  • 小さなペイロードサイズ: 圧縮無効時でも平均61%、圧縮有効時で80%のサイズ削減
  • 柔軟なシリアライゼーション: java.io.Serializableを実装していないクラスもシリアライズ可能
  • 成熟した実装: Twitter、Groupon、Yahoo、Hive、Stormなど多くの本番環境で採用
  • 簡単なAPI: 直感的で使いやすいAPIデザイン
  • オブジェクトコピー機能: ディープコピー・シャローコピーを簡単に実装可能

デメリット

  • バージョン互換性の欠如: リリース間でのシリアライゼーション互換性が保証されない
  • 長期保存に不適: データの長期保存には推奨されない
  • スレッドセーフでない: マルチスレッド環境では適切な同期処理やプーリングが必要
  • 登録の必要性: 最高のパフォーマンスを得るにはクラスの事前登録が必要
  • 引数なしコンストラクタ必要: 最適なパフォーマンスのために引数なしコンストラクタが必要

参考ページ

書き方の例

基本的な使い方

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.*;

public class KryoExample {
    public static void main(String[] args) throws Exception {
        Kryo kryo = new Kryo();
        
        // クラスの登録(パフォーマンス向上のため)
        kryo.register(Person.class);
        
        Person person = new Person("田中太郎", 30);
        
        // ファイルにシリアライズ
        Output output = new Output(new FileOutputStream("person.bin"));
        kryo.writeObject(output, person);
        output.close();
        
        // ファイルからデシリアライズ
        Input input = new Input(new FileInputStream("person.bin"));
        Person restoredPerson = kryo.readObject(input, Person.class);
        input.close();
        
        System.out.println(restoredPerson);
    }
    
    static class Person {
        String name;
        int age;
        
        // Kryoには引数なしコンストラクタが必要
        public Person() {}
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
}

クラスが不明な場合のシリアライゼーション

// クラス情報と一緒に書き込み
kryo.writeClassAndObject(output, someObject);

// クラス情報と一緒に読み込み
Object object = kryo.readClassAndObject(input);

// nullの可能性がある場合
kryo.writeObjectOrNull(output, maybeNullObject);
SomeClass object = kryo.readObjectOrNull(input, SomeClass.class);

カスタムシリアライザーの実装

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.time.LocalDateTime;

public class LocalDateTimeSerializer extends Serializer<LocalDateTime> {
    @Override
    public void write(Kryo kryo, Output output, LocalDateTime dateTime) {
        output.writeLong(dateTime.toEpochSecond(java.time.ZoneOffset.UTC));
        output.writeInt(dateTime.getNano());
    }
    
    @Override
    public LocalDateTime read(Kryo kryo, Input input, Class<LocalDateTime> type) {
        long epochSecond = input.readLong();
        int nano = input.readInt();
        return LocalDateTime.ofEpochSecond(epochSecond, nano, java.time.ZoneOffset.UTC);
    }
}

// 使用例
Kryo kryo = new Kryo();
kryo.register(LocalDateTime.class, new LocalDateTimeSerializer());

スレッドセーフな使用(プーリング)

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.util.Pool;

public class KryoPool {
    private static final Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
        protected Kryo create() {
            Kryo kryo = new Kryo();
            // 必要なクラスを登録
            kryo.register(Person.class);
            kryo.register(Address.class);
            // その他の設定
            return kryo;
        }
    };
    
    public static byte[] serialize(Object object) {
        Kryo kryo = kryoPool.obtain();
        try (Output output = new Output(1024, -1)) {
            kryo.writeClassAndObject(output, object);
            return output.toBytes();
        } finally {
            kryoPool.free(kryo);
        }
    }
    
    public static Object deserialize(byte[] bytes) {
        Kryo kryo = kryoPool.obtain();
        try (Input input = new Input(bytes)) {
            return kryo.readClassAndObject(input);
        } finally {
            kryoPool.free(kryo);
        }
    }
}

参照の有効化/無効化

Kryo kryo = new Kryo();

// 参照を有効化(デフォルト)- 循環参照を処理できるが遅い
kryo.setReferences(true);

// 参照を無効化 - 高速だが循環参照でスタックオーバーフロー
kryo.setReferences(false);

// 特定のシリアライザーのみ参照を無効化
kryo.register(SomeClass.class, new FieldSerializer(kryo, SomeClass.class) {{
    setReferences(false);
}});

オブジェクトのコピー

Kryo kryo = new Kryo();

// ディープコピー
Person originalPerson = new Person("山田花子", 25);
Person deepCopy = kryo.copy(originalPerson);

// シャローコピー
Person shallowCopy = kryo.copyShallow(originalPerson);

// コピー時の設定
kryo.setCopyReferences(true);  // 参照を保持
kryo.setCopyTransient(false);  // transientフィールドをコピーしない

Maven依存関係

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

Gradle依存関係

implementation 'com.esotericsoftware:kryo:5.6.2'