您好,登錄后才能下訂單哦!
本篇內容主要講解“Java Kryo,Protostuff,Hessian序列化方式是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java Kryo,Protostuff,Hessian序列化方式是什么”吧!
前段時間在寫RPC框架的時候用到了Kryo、Hessian、Protostuff三種序列化方式。但是當時因為急于實現功能,就只是簡單的的看了一下如何使用這三種序列化方式,并沒有去深入研究各自的特性,以及優點和缺點。知道現在就將RPC框架寫完了之后,才有時間靜下心來對三種方式做一個對比,總結。
Kryo、Hessain、Protostuff都是第三方開源的序列化/反序列化框架,要了解其各自的特性,我們首先需要知道序列化/反序列化是什么:
序列化:就是將對象轉化成字節序列的過程。
反序列化:就是講字節序列轉化成對象的過程。
seriallization 序列化 : 將對象轉化為便于傳輸的格式, 常見的序列化格式:二進制格式,字節數組,json字符串,xml字符串。
deseriallization 反序列化:將序列化的數據恢復為對象的過程
如果對序列化相關概念還不是很清楚的同學可以參照美團技術團隊的序列化和反序列化
我們先創建一個新的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); } } }
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); } }
運行截圖:
由圖可知,Hessian序列化后字節所占的空間都顯著比其他兩種方式要大得多
Hessian使用固定長度存儲int和long,而kryo使用變長的int和long保證這種基本數據類型序列化后盡量小,實際應用中,很大的數據不會經常出現。
Kryo進行序列化的時候,需要傳入完整類名或者利用 register() 提前將類注冊到Kryo上,其類與一個int型的ID相關聯,序列中只存放這個ID,因此序列體積就更小,而Hessian則是將所有類字段信息都放入序列化字節數組中,直接利用字節數組進行反序列化,不需要其他參與,因為存的東西多處理速度就會慢點
Kryo使用不需要實現Serializable接口,Hessian則需實現
Kryo數據類的字段增、減,序列化和反序列化時無法兼容,而Hessian則兼容,Protostuff是只能在末尾添加新字段才兼容
Kryo和Hessian使用涉及到的數據類中必須擁有無參構造函數
Hessian會把復雜對象的所有屬性存儲在一個Map中進行序列化。所以在父類、子類存在同名成員變量的情況下,Hessian序列化時,先序列化子類,然后序列化父類,因此反序列化結果會導致子類同名成員變量被父類的值覆蓋
Kryo不是線程安全的,要通過ThreadLocal或者創建Kryo線程池來保證線程安全,而Protostuff則是線程安全的
Protostuff和Kryo序列化的格式有相似之處,都是利用一個標記來記錄字段類型,因此序列化出來體積都比較小
優點 | 缺點 | |
---|---|---|
Kryo | 速度快,序列化后體積小 | 跨語言支持較復雜 |
Hessian | 默認支持跨語言 | 較慢 |
Protostuff | 速度快,基于protobuf | 需靜態編譯 |
Protostuff-Runtime | 無需靜態編譯,但序列化前需預先傳入schema | 不支持無默認構造函數的類,反序列化時需用戶自己初始化序列化后的對象,其只負責將該對象進行賦值 |
Java | 使用方便,可序列化所有類 | 速度慢,占空間 |
到此,相信大家對“Java Kryo,Protostuff,Hessian序列化方式是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。