Java 二进制序列化 简介 为什么需要二进制序列化库 原因很简单,就是 Java 默认的序列化机制(ObjectInputStream
和 ObjectOutputStream
)具有很多缺点。
不了解 Java 默认的序列化机制,可以参考:Java 序列化
Java 自身的序列化方式具有以下缺点:
无法跨语言使用 。这点最为致命,对于很多需要跨语言通信的异构系统来说,不能跨语言序列化,即意味着完全无法通信(彼此数据不能识别,当然无法交互了)。
序列化的性能不高 。序列化后的数据体积较大,这大大影响存储和传输的效率。
序列化一定需要实现 Serializable
接口。
需要关注 serialVersionUID
。
引入二进制序列化库就是为了解决这些问题,这在 RPC 应用中尤为常见。
主流序列化库简介 Protobuf Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储 格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL 编译器,生成序列化工具类。
优点:
序列化后体积相比 JSON、Hessian 小很多
序列化反序列化速度很快,不需要通过反射获取类型
语言和平台无关(基于 IDL),IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器
消息格式升级和兼容性不错,可以做到后向兼容
支持 Java, C++, Python 三种语言
缺点:
Protobuf 对于具有反射和动态能力的语言来说,用起来很费劲。
Thrift
Thrift 是 apache 开源项目,是一个点对点的 RPC 实现。
它具有以下特性:
支持多种语言(目前支持 28 种语言,如:C++、go、Java、Php、Python、Ruby 等等)。
使用了组建大型数据交换及存储工具,对于大型系统中的内部数据传输,相对于 Json 和 xml 在性能上和传输大小上都有明显的优势。
支持三种比较典型的编码方式(通用二进制编码,压缩二进制编码,优化的可选字段压缩编解码)。
Hessian Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协 议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节 数也更小。
RPC 框架 Dubbo 就支持 Thrift 和 Hession。
它具有以下特性:
支持多种语言。如:Java、Python、C++、C#、PHP、Ruby 等。
相对其他二进制序列化库较慢。
Hessian 本身也有问题,官方版本对 Java 里面一些常见对象的类型不支持:
Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserializer 类修复;
Locale 类,可以通过扩展 ContextSerializerFactory 类修复;
Byte/Short 反序列化的时候变成 Integer。
Kryo
Kryo 是用于 Java 的快速高效的二进制对象图序列化框架。Kryo 还可以执行自动的深拷贝和浅拷贝。 这是从对象到对象的直接复制,而不是从对象到字节的复制。
它具有以下特性:
速度快,序列化体积小
官方不支持 Java 以外的其他语言
FST
FST 是一个 Java 实现二进制序列化库。
它具有以下特性:
近乎于 100% 兼容 JDK 序列化,且比 JDK 原序列化方式快 10 倍
2.17 开始与 Android 兼容
(可选)2.29 开始支持将任何可序列化的对象图编码/解码为 JSON(包括共享引用)
小结 了解了以上这些常见的二进制序列化库的特性。在技术选型时,我们就可以做到有的放矢。
(1)选型参考依据
对于二进制序列化库,我们的选型考量一般有以下几点:
是否支持跨语言
根据业务实际需求来决定。一般来说,支持跨语言,为了兼容,使用复杂度上一般会更高一些。
序列化、反序列化的性能
类库是否轻量化,API 是否简单易懂
(2)选型建议
FST 应用 引入依赖 1 2 3 4 5 <dependency > <groupId > de.ruedigermoeller</groupId > <artifactId > fst</artifactId > <version > 2.56</version > </dependency >
FST API 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import org.nustaq.serialization.FSTConfiguration;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.Base64;public class FstDemo { private static FSTConfiguration DEFAULT_CONFIG = FSTConfiguration.createDefaultConfiguration(); public static <T> byte [] writeToBytes(T obj) { return DEFAULT_CONFIG.asByteArray(obj); } public static <T> String writeToString (T obj) { byte [] bytes = writeToBytes(obj); return new String (Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); } public static <T> T readFromBytes (byte [] bytes, Class<T> clazz) throws IOException { Object obj = DEFAULT_CONFIG.asObject(bytes); if (clazz.isInstance(obj)) { return (T) obj; } else { throw new IOException ("derialize failed" ); } } public static <T> T readFromString (String str, Class<T> clazz) throws IOException { byte [] bytes = str.getBytes(StandardCharsets.UTF_8); return readFromBytes(Base64.getDecoder().decode(bytes), clazz); } }
测试:
1 2 3 4 5 6 7 8 long begin = System.currentTimeMillis();for (int i = 0 ; i < BATCH_SIZE; i++) { TestBean oldBean = BeanUtils.initJdk8Bean(); byte [] bytes = FstDemo.writeToBytes(oldBean); TestBean newBean = FstDemo.readFromBytes(bytes, TestBean.class); } long end = System.currentTimeMillis();System.out.printf("FST 序列化/反序列化耗时:%s" , (end - begin));
Kryo 应用 引入依赖 1 2 3 4 5 <dependency > <groupId > com.esotericsoftware</groupId > <artifactId > kryo</artifactId > <version > 5.0.0-RC4</version > </dependency >
Kryo API 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Input;import com.esotericsoftware.kryo.io.Output;import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;import org.objenesis.strategy.StdInstantiatorStrategy;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.nio.charset.StandardCharsets;import java.util.Base64;public class KryoDemo { private static final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> { Kryo kryo = new Kryo (); kryo.setReferences(true ); kryo.setRegistrationRequired(false ); ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy ()); return kryo; }); public static Kryo getInstance () { return kryoLocal.get(); } public static <T> byte [] writeToBytes(T obj) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); Output output = new Output (byteArrayOutputStream); Kryo kryo = getInstance(); kryo.writeObject(output, obj); output.flush(); return byteArrayOutputStream.toByteArray(); } public static <T> String writeToString (T obj) { byte [] bytes = writeToBytes(obj); return new String (Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); } @SuppressWarnings("unchecked") public static <T> T readFromBytes (byte [] bytes, Class<T> clazz) { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); Input input = new Input (byteArrayInputStream); Kryo kryo = getInstance(); return (T) kryo.readObject(input, clazz); } public static <T> T readFromString (String str, Class<T> clazz) { byte [] bytes = str.getBytes(StandardCharsets.UTF_8); return readFromBytes(Base64.getDecoder().decode(bytes), clazz); } }
测试:
1 2 3 4 5 6 7 8 long begin = System.currentTimeMillis();for (int i = 0 ; i < BATCH_SIZE; i++) { TestBean oldBean = BeanUtils.initJdk8Bean(); byte [] bytes = KryoDemo.writeToBytes(oldBean); TestBean newBean = KryoDemo.readFromBytes(bytes, TestBean.class); } long end = System.currentTimeMillis();System.out.printf("Kryo 序列化/反序列化耗时:%s" , (end - begin));
Hessian 应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Student student = new Student ();student.setNo(101 ); student.setName("HESSIAN" ); ByteArrayOutputStream bos = new ByteArrayOutputStream ();Hessian2Output output = new Hessian2Output (bos);output.writeObject(student); output.flushBuffer(); byte [] data = bos.toByteArray();bos.close(); ByteArrayInputStream bis = new ByteArrayInputStream (data);Hessian2Input input = new Hessian2Input (bis);Student deStudent = (Student) input.readObject();input.close(); System.out.println(deStudent);
参考资料