首页 > 代码库 > 再次认识java的序列化

再次认识java的序列化

首先是我们为什么要序列化?

我们可以来想想游戏的场景,一个游戏有很多关卡,并不是一次性能够打完的。如果我们打完一关,这时候需要退出游戏休息了。当我们再次进入游戏之后发现这个游戏竟然需要重新打起,我们接下来做的第一件事情一定是卸载。。

如何解决上面的问题呢?其实一个游戏会有很多个存档点,当你进入一个存档点之后,就会对之前的进度进行保存。这里提到了保存,那么如何进行保存呢?

这里就延伸到了java的持久化上。java的持久化方式有很多种,比如说数据库,写文件等。这里我们单说序列化,关键为什么是序列化呢,它到底有什么好处呢?对于我来说,其实最大的好处就是简单,只要需要序列化的对象实现了Serializable接口,重点是:序列化接口没有方法或字段!也就是将想要序列化的对象中加入implements Serializable就可以了,是不是很简单。

序列化过程到底做了什么呢?序列化过程其实就是通过writeObject方法,将对象的数据、状态转化为字节流持久化到文件中。反序列化其实就是通过readObject实现的一个逆过程。


序列化中有几个特殊的点需要注意一下:

第一点是serialVersionUID,java强烈建议在需要序列化的类中显式的声明该值。该值实际上就是起到一个版本号的作用,在解序列化时,会通过该值判断类的版本有没有发生变化。如果发生了变化则在解序列化时会弹出InvalidClassException的错误。为什么呢?因为类的版本变化之后,比如新增了一个属性,或者减少了一个属性。这样在解序列化时肯定会出现错误。

如果不显示的声明serialVersionUID会出现什么问题?如果不显示的声明,序列化运行时将针对该类计算一个默认的serialVersionUID值。默认是怎么计算出来的呢?计算的逻辑在这里逻辑

我们可以看到计算过程还是蛮复杂的,如果你有心情看的话。。。其实里面再次注明了强烈建议显示的指定serialVersionUID。为什么这么强烈的建议呢?存在这样的情况,客户端与服务端架构不同,jvm也不同,当客户端将序列化的对象传递给服务端时,客户端序列化时计算的id与服务端计算的id这时候不一样,这就悲剧了。为了防止悲剧发生,还是手动指定的好

第二点是当一个类中不是所有的属性都需要序列化时怎么办?

这时我们就要用到transient关键字,在定义时做如下处理即可

transient private Integer age

第三点是Externalizable,如果我们觉得Serializable不够灵活,我们可以使用Externalizable。该接口继承于序列化接口并增加了两个方法writeExternal 和 readExternal分别在序列化与反序列化时调用。两方法是用来代替定制的writeObject与readObject方法。想要怎么序列化都靠你自己发挥了

第四点是单例模式下的序列化与反序列化

单例模式就是为了对象的唯一性,我们在序列化与反序列化后就会多出来一个对象,怎么办?

这时候我们就可以用writeReplace 和 readResolve 方法来指派一个替代对象,比如在对象类中增加readResolve方法,该方法直接将单例返回,这样在反序列化时就不会多出来一个对象了。


再次认识java的序列化