Java|几种Java常用序列化框架的选型与对比( 二 )


java.io.InvalidClassException: com.yjz.serialization.java.UserInfo; local classincompatible: streamclassdescserialVersionUID = -5548195544707231683 local classserialVersionUID = -5194320341014913710
上面这种情况 , 是由于我们没有定义serialVersionUID , 而是由JDK自动hash生成的 , 所以序列化与反序列化前后结果不一致 。
但是我们可以通过自定义serialVersionUID方式来规避掉这种情况(序列化前后都是使用定义的serialVersionUID) , 这样JDK Serializable就可以支持字段扩展了 。
privatestaticfinallong serialVersionUID = 1L;
性能
JDK Serializable是Java自带的序列化框架 , 但是在性能上其实一点不像亲生的 。 下面测试用例是我们贯穿全文的一个测试实体 。
publicclass MessageInfo implements Serializable {
privateString username;privateString password;private int age;private HashMap<StringObject> params;...publicstatic MessageInfo buildMessage() {MessageInfo messageInfo = new MessageInfo();messageInfo.setUsername(\"abcdefg\");messageInfo.setPassword(\"123456789\");messageInfo.setAge(27);Map<StringObject> map = new HashMap<>();for(int i = 0; i< 20; i++) {map.put(String.valueOf(i)\"a\");return messageInfo;
使用JDK序列化后字节大小为:432 。 光看这组数字也许不会感觉到什么 , 之后我们会拿这个数据和其它序列化框架进行对比 。
我们对该测试用例进行1000万次序列化 , 然后计算时间总和:
同样我们之后会同其它序列化框架进行对比 。
数据类型和语法结构支持性
由于JDK Serializable是Java语法原生序列化框架 , 所以基本都能够支持Java数据类型和语法 。
WeakHashMap没有实现Serializable接口 。
注1:但我们要序列化下面代码:
Runnable runnable = () -> System.out.println(\"Hello\");
直接序列化会得到以下异常:
com.yjz.serialization.SerializerFunctionTest$$Lambda$1/189568618
原因就是我们Runnable的Lambda并没有实现Serializable接口 。 我们可以做如下修改 , 即可支持Lambda表达式序列化 。
Runnable runnable = (Runnable & Serializable) () -> System.out.println(\"Hello\");
2FST序列化框架
FST(fast-serialization)是完全兼容JDK序列化协议的Java序列化框架 , 它在序列化速度上能达到JDK的10倍 , 序列化结果只有JDK的1/3 。 目前FST的版本为2.56 , 在2.17版本之后提供了对Android的支持 。
下面是使用FST序列化的Demo , FSTConfiguration是线程安全的 , 但是为了防止频繁调用时其成为性能瓶颈 , 一般会使用TreadLocal为每个线程分配一个FSTConfiguration 。
private final ThreadLocal<FSTConfiguration> conf = ThreadLocal.withInitial(() -> {FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();return conf;);
publicbyte[
encoder(Object object) {return conf.get().asByteArray(object);
public <T> T decoder(byte[
bytes) {Object ob = conf.get().asObject(bytes);return (T)ob;
通用性
FST同样是针对Java而开发的序列化框架 , 所以也不存在跨语言特性 。
易用性
在易用性上 , FST可以说能够甩JDK Serializable几条街 , 语法极其简洁 , FSTConfiguration封装了大部分方法 。
可扩展性
FST通过@Version注解能够支持新增字段与旧的数据流兼容 。 对于新增的字段都需要通过@Version注解标识 , 没有版本注释意味着版本为0 。
privateString origiField;@Version(1)privateString addField;
注意:
删除字段将破坏向后兼容性 , 但是如果我们在原始字段情况下删除字段是能够向后兼容的(没有新增任何字段) 。 但是如果新增字段后 , 再删除字段的话就会破坏其兼容性 。
Version注解功能不能应用于自己实现的readObject/writeObject情况 。
如果自己实现了Serializer , 需要自己控制Version 。
综合来看 , FST在扩展性上面虽然支持 , 但是用起来还是比较繁琐的 。
性能
使用FST序列化上面的测试用例 , 序列化后大小为:172 , 相比JDK序列化的432, 将近减少了1/3 。 下面我们再看序列化与反序列化的时间开销 。
我们可以优化一下FST , 将循环引用判断关闭 , 并且对序列化类进行余注册 。
privatestaticfinalThreadLocal<FSTConfiguration> conf = ThreadLocal.withInitial(() -> {FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();conf.registerClass(UserInfo.class);conf.setShareReferences(false);return conf;);
通过上面的优化配置 , 得到的时间开销如下:
可以看到序列化时间将近提升了2倍 , 但是通过优化后的序列化数据大小增长到了191。
数据类型和语法结构支持性
FST是基于JDK序列化框架而进行开发的 , 所以在数据类型和语法上和Java支持性一致 。