Java で一般的に使用されるシリアル化メソッドは何ですか? Kryo、Protostuff、Hessian を例として、その実装原理を説明します。
まえがき
少し前に、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>
##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); } } }
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(); } } } } }
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 つの方法よりも大幅に大きくなります。
- 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 サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

Java は、初心者と経験豊富な開発者の両方が学習できる人気のあるプログラミング言語です。このチュートリアルは基本的な概念から始まり、高度なトピックに進みます。 Java Development Kit をインストールしたら、簡単な「Hello, World!」プログラムを作成してプログラミングを練習できます。コードを理解したら、コマンド プロンプトを使用してプログラムをコンパイルして実行すると、コンソールに「Hello, World!」と出力されます。 Java の学習はプログラミングの旅の始まりであり、習熟が深まるにつれて、より複雑なアプリケーションを作成できるようになります。
