Java で一般的に使用されるシリアル化メソッドは何ですか? Kryo、Protostuff、Hessian を例として、その実装原理を説明します。

王林
リリース: 2023-05-07 21:01:07
転載
912 人が閲覧しました

    まえがき

    少し前に、RPC フレームワークを作成するときに、Kryo、Hessian、Protostuff という 3 つのシリアル化メソッドを使用しました。ただし、当時は機能を実装したいという気持ちが強かったため、これら 3 つのシリアル化メソッドの使用方法を簡単に説明しただけで、それぞれの特徴やメリット、デメリットについては深く掘り下げませんでした。 RPC フレームワークの作成が完了したことがわかったので、落ち着いて 3 つの方法を比較して要約する時間があります。

    Kryo、Hessain、および Protostuff はすべて、サードパーティのオープン ソースのシリアル化/逆シリアル化フレームワークです。それぞれの特性を理解するには、まずシリアル化/逆シリアル化とは何かを知る必要があります:

    シリアル化: は、オブジェクトをバイト シーケンスに変換するプロセスです。

    逆シリアル化: は、バイト シーケンスをオブジェクトに変換するプロセスです。

    serialization シリアル化: オブジェクトを送信に便利な形式に変換します。一般的なシリアル化形式: バイナリ形式、バイト配列、json 文字列、xml 文字列。

    デシリアライゼーション デシリアライゼーション: シリアル化されたデータをオブジェクトに復元するプロセス

    シリアル化に関連する概念がよくわからない場合は、Meituan を参照してください。技術チームのシリアル化と逆シリアル化

    パフォーマンスの比較

    事前準備

    • 最初に新しい Maven プロジェクトを作成します

    • 次に、依存関係をインポートします

    • ##
      <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
          <version>5.8.2</version>
          <scope>test</scope>
      </dependency>
      <!-- 代码简化 -->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.20</version>
      </dependency>
      <!--kryo-->
      <dependency>
          <groupId>com.esotericsoftware</groupId>
          <artifactId>kryo-shaded</artifactId>
          <version>4.0.2</version>
      </dependency>
      <dependency>
          <groupId>commons-codec</groupId>
          <artifactId>commons-codec</artifactId>
          <version>1.10</version>
      </dependency>
      <!--protostuff-->
      <dependency>
          <groupId>io.protostuff</groupId>
          <artifactId>protostuff-core</artifactId>
          <version>1.7.2</version>
      </dependency>
      <dependency>
          <groupId>io.protostuff</groupId>
          <artifactId>protostuff-runtime</artifactId>
          <version>1.7.2</version>
      </dependency>
      <!--hessian2-->
      <dependency>
          <groupId>com.caucho</groupId>
          <artifactId>hessian</artifactId>
          <version>4.0.62</version>
      </dependency>
      ログイン後にコピー
    #Tool class:

    ##kryo

    package cuit.pymjl.utils;
    import com.esotericsoftware.kryo.Kryo;
    import com.esotericsoftware.kryo.io.Input;
    import com.esotericsoftware.kryo.io.Output;
    import org.apache.commons.codec.binary.Base64;
    import org.objenesis.strategy.StdInstantiatorStrategy;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.UnsupportedEncodingException;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/4/18 20:07
     **/
    @SuppressWarnings("all")
    public class KryoUtils {
        private static final String DEFAULT_ENCODING = "UTF-8";
    
        //每个线程的 Kryo 实例
        private static final ThreadLocal<Kryo> KRYO_LOCAL = new ThreadLocal<Kryo>() {
            @Override
            protected Kryo initialValue() {
                Kryo kryo = new Kryo();
    
                /**
                 * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化,
                 * 上线的同时就必须清除 Redis 里的所有缓存,
                 * 否则那些缓存再回来反序列化的时候,就会报错
                 */
                //支持对象循环引用(否则会栈溢出)
                kryo.setReferences(true); //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置
    
                //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册)
                kryo.setRegistrationRequired(false); //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置
    
                //Fix the NPE bug when deserializing Collections.
                ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
                        .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
    
                return kryo;
            }
        };
        /**
         * 获得当前线程的 Kryo 实例
         *
         * @return 当前线程的 Kryo 实例
         */
        public static Kryo getInstance() {
            return KRYO_LOCAL.get();
        }
    
        //-----------------------------------------------
        //          序列化/反序列化对象,及类型信息
        //          序列化的结果里,包含类型的信息
        //          反序列化时不再需要提供类型
        //-----------------------------------------------
    
        /**
         * 将对象【及类型】序列化为字节数组
         *
         * @param obj 任意对象
         * @param <T> 对象的类型
         * @return 序列化后的字节数组
         */
        public static <T> byte[] writeToByteArray(T obj) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Output output = new Output(byteArrayOutputStream);
    
            Kryo kryo = getInstance();
            kryo.writeClassAndObject(output, obj);
            output.flush();
    
            return byteArrayOutputStream.toByteArray();
        }
        /**
         * 将对象【及类型】序列化为 String
         * 利用了 Base64 编码
         *
         * @param obj 任意对象
         * @param <T> 对象的类型
         * @return 序列化后的字符串
         */
        public static <T> String writeToString(T obj) {
            try {
                return new String(Base64.encodeBase64(writeToByteArray(obj)), DEFAULT_ENCODING);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        /**
         * 将字节数组反序列化为原对象
         *
         * @param byteArray writeToByteArray 方法序列化后的字节数组
         * @param <T>       原对象的类型
         * @return 原对象
         */
        @SuppressWarnings("unchecked")
        public static <T> T readFromByteArray(byte[] byteArray) {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
            Input input = new Input(byteArrayInputStream);
    
            Kryo kryo = getInstance();
            return (T) kryo.readClassAndObject(input);
        }
        /**
         * 将 String 反序列化为原对象
         * 利用了 Base64 编码
         *
         * @param str writeToString 方法序列化后的字符串
         * @param <T> 原对象的类型
         * @return 原对象
         */
        public static <T> T readFromString(String str) {
            try {
                return readFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)));
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        //-----------------------------------------------
        //          只序列化/反序列化对象
        //          序列化的结果里,不包含类型的信息
        //-----------------------------------------------
    
        /**
         * 将对象序列化为字节数组
         *
         * @param obj 任意对象
         * @param <T> 对象的类型
         * @return 序列化后的字节数组
         */
        public static <T> byte[] writeObjectToByteArray(T obj) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Output output = new Output(byteArrayOutputStream);
            Kryo kryo = getInstance();
            kryo.writeObject(output, obj);
            output.flush();
            return byteArrayOutputStream.toByteArray();
        }
    
        /**
         * 将对象序列化为 String
         * 利用了 Base64 编码
         *
         * @param obj 任意对象
         * @param <T> 对象的类型
         * @return 序列化后的字符串
         */
        public static <T> String writeObjectToString(T obj) {
            try {
                return new String(Base64.encodeBase64(writeObjectToByteArray(obj)), DEFAULT_ENCODING);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        /**
         * 将字节数组反序列化为原对象
         *
         * @param byteArray writeToByteArray 方法序列化后的字节数组
         * @param clazz     原对象的 Class
         * @param <T>       原对象的类型
         * @return 原对象
         */
        @SuppressWarnings("unchecked")
        public static <T> T readObjectFromByteArray(byte[] byteArray, Class<T> clazz) {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
            Input input = new Input(byteArrayInputStream);
    
            Kryo kryo = getInstance();
            return kryo.readObject(input, clazz);
        }
        /**
         * 将 String 反序列化为原对象
         * 利用了 Base64 编码
         *
         * @param str   writeToString 方法序列化后的字符串
         * @param clazz 原对象的 Class
         * @param <T>   原对象的类型
         * @return 原对象
         */
        public static <T> T readObjectFromString(String str, Class<T> clazz) {
            try {
                return readObjectFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)), clazz);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
    }
    ログイン後にコピー
    Hessian

    package cuit.pymjl.utils;
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/2 12:39
     **/
    public class HessianUtils {
        /**
         * 序列化
         *
         * @param obj obj
         * @return {@code byte[]}
         */
        public static byte[] serialize(Object obj) {
            Hessian2Output ho = null;
            ByteArrayOutputStream baos = null;
            try {
                baos = new ByteArrayOutputStream();
                ho = new Hessian2Output(baos);
                ho.writeObject(obj);
                ho.flush();
                return baos.toByteArray();
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException("serialize failed");
            } finally {
                if (null != ho) {
                    try {
                        ho.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != baos) {
                    try {
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        /**
         * 反序列化
         *
         * @param bytes 字节
         * @param clazz clazz
         * @return {@code T}
         */
        public static  <T> T deserialize(byte[] bytes, Class<T> clazz) {
            Hessian2Input hi = null;
            ByteArrayInputStream bais = null;
            try {
                bais = new ByteArrayInputStream(bytes);
                hi = new Hessian2Input(bais);
                Object o = hi.readObject();
                return clazz.cast(o);
            } catch (Exception ex) {
                throw new RuntimeException("deserialize failed");
            } finally {
                if (null != hi) {
                    try {
                        hi.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != bais) {
                    try {
                        bais.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    ログイン後にコピー
    Protostuff

    package cuit.pymjl.utils;
    import io.protostuff.LinkedBuffer;
    import io.protostuff.ProtostuffIOUtil;
    import io.protostuff.Schema;
    import io.protostuff.runtime.RuntimeSchema;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/6/28 21:00
     **/
    public class ProtostuffUtils {
        /**
         * 避免每次序列化都重新申请Buffer空间
         * 这个字段表示,申请一个内存空间用户缓存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申请了默认大小的空间512个字节,
         * 我们也可以使用MIN_BUFFER_SIZE,表示256个字节。
         */
        private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        /**
         * 缓存Schema
         * 这个字段表示缓存的Schema。那这个Schema是什么呢?就是一个组织结构,就好比是数据库中的表、视图等等这样的组织机构,
         * 在这里表示的就是序列化对象的结构。
         */
        private static final Map<Class<?>, Schema<?>> SCHEMA_CACHE = new ConcurrentHashMap<>();
    
        /**
         * 序列化方法,把指定对象序列化成字节数组
         *
         * @param obj 对象
         * @return byte[]
         */
        @SuppressWarnings("unchecked")
        public static <T> byte[] serialize(T obj) {
            Class<T> clazz = (Class<T>) obj.getClass();
            Schema<T> schema = getSchema(clazz);
            byte[] data;
            try {
                data = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
            } finally {
                BUFFER.clear();
            }
            return data;
        }
    
        /**
         * 反序列化方法,将字节数组反序列化成指定Class类型
         *
         * @param data  字节数组
         * @param clazz 字节码
         * @return
         */
        public static <T> T deserialize(byte[] data, Class<T> clazz) {
            Schema<T> schema = getSchema(clazz);
            T obj = schema.newMessage();
            ProtostuffIOUtil.mergeFrom(data, obj, schema);
            return obj;
        }
        @SuppressWarnings("unchecked")
        private static <T> Schema<T> getSchema(Class<T> clazz) {
            Schema<T> schema = (Schema<T>) SCHEMA_CACHE.get(clazz);
            if (schema == null) {
                schema = RuntimeSchema.getSchema(clazz);
                if (schema == null) {
                    SCHEMA_CACHE.put(clazz, schema);
                }
            }
            return schema;
        }
    }
    ログイン後にコピー
    テスト用のエンティティ クラスを作成します:

    package cuit.pymjl.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.io.Serial;
    import java.io.Serializable;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/2 12:32
     **/
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student implements Serializable {
        @Serial
        private static final long serialVersionUID = -91809837793898L;
    
        private String name;
        private String password;
        private int age;
        private String address;
        private String phone;
    }
    ログイン後にコピー
    空間の比較シリアル化後にバイトが占有

    テスト クラスの書き込み:

    public class MainTest {
        @Test
        void testLength() {
            Student student = new Student("pymjl", "123456", 18, "北京", "123456789");
            int kryoLength = KryoUtils.writeObjectToByteArray(student).length;
            int hessianLength = HessianUtils.serialize(student).length;
            int protostuffLength = ProtostuffUtils.serialize(student).length;
            System.out.println("kryoLength: " + kryoLength);
            System.out.println("hessianLength: " + hessianLength);
            System.out.println("protostuffLength: " + protostuffLength);
        }
    }
    ログイン後にコピー
    スクリーンショットの実行:

    図からわかるように、ヘッセ行列のシリアル化後にバイトが占めるスペースは、他の 2 つの方法よりも大幅に大きくなります。Java で一般的に使用されるシリアル化メソッドは何ですか? Kryo、Protostuff、Hessian を例として、その実装原理を説明します。

    その他の比較

    ヘッセ行列は固定長を使用しますkryo は、この基本データ型がシリアル化後に可能な限り小さくなるようにするために、可変長の int と Long を使用しますが、実際のアプリケーションでは、大きなデータが頻繁に表示されることはありません。
    • Kryo がシリアル化するときは、完全なクラス名を渡すか、 register() を使用して事前にクラスを Kryo に登録する必要があります。そのクラスは int 型 ID に関連付けられており、シーケンスにはこの ID のみが格納されるため、シーケンスのボリュームは小さくなりますが、ヘッシアンではすべてのクラス フィールド情報がシリアル化されたバイト配列に配置され、他の関与なしに、そのバイト配列が逆シリアル化に直接使用されます。 -処理が遅くなります
    • #Kryo は Serializable インターフェイスを実装する必要はありませんが、Hessian は Kryo データ クラスのフィールド増加を実装する必要があります

    • 、マイナス、シリアル化と逆シリアル化は互換性がありませんが、ヘシアンは互換性があります。Protostuff は最後に新しいフィールドを追加することによってのみ互換性があります。

    • #Kryo とヘシアンの使用には、データ クラスには引数のないコンストラクターが必要です。

    • #Hessian は、複雑なオブジェクトのすべてのプロパティをシリアル化のために Map に格納します。そのため、親クラスとサブクラスに同名のメンバ変数が存在する場合、ヘッセ行列化の際、サブクラスが先にシリアル化され、その後親クラスがシリアル化されるため、逆シリアル化の結果、同名のメンバ変数が発生します。

    • Kryo はスレッド セーフではありません。スレッド セーフを確保するには、ThreadLocal を使用するか、Kryo スレッド プールを作成する必要があります。 Protostuff はスレッドセーフです
    • Protostuff と Kryo シリアル化の形式は似ています。どちらもフィールド タイプを記録するためにマークを使用するため、シリアル化される量は比較的小さくなります。
    • 概要

    メリットデメリットクロス言語サポートは複雑です低速静的コンパイルが必要デフォルトのコンストラクターのないクラスはサポートされません。ユーザーは逆シリアル化中にシリアル化されたオブジェクトを自分で初期化する必要があります、オブジェクトの割り当てのみを担当します#遅くてスペースを消費します
    Kryo 高速、シリアル化後のサイズは小さい
    Hessian デフォルトのクロス言語サポート
    Protostuff 高速、protobufに基づく
    Protostuff-Runtime 静的コンパイルは必要ありませんが、シリアル化の前にスキーマを渡す必要があります
    Java 使いやすく、すべてのクラスをシリアル化できます

    以上がJava で一般的に使用されるシリアル化メソッドは何ですか? Kryo、Protostuff、Hessian を例として、その実装原理を説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    関連ラベル:
    ソース:yisu.com
    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート