Java 序列化的基本概念
答案参考自:
什么是序列化?
序列化: 把对象转化为可传输的字节序列过程称为序列化。
反序列化: 把字节序列还原为对象的过程称为反序列化。
为什么需要使用序列化和反序列化?
目的是为了对象可以跨平台存储,和进行网络传输。
序列化的有哪些好处
对对象进行序列化操作,可以极大程度的方便传输。
什么情况下需要序列化
- 当你想把的内存中的对象保存到一个文件中或者数据库中时候。
- 当你想用序列化在网络上传送对象的时候。
什么是serialVersionUID
serialVersionUID
是用来辅助序列化和反序列化的过程。原则上序列化后的数据中的serialVersionUID
只有和当前类的serialVersionUID
一致才能成功的反序列化。
为什么还要显示指定serialVersionUID
的值?
serialVersionUID
的详细工作机制是这样的:
序列化的时候系统会把当前类的serialVersionUID
写入序列化的文件中(也可能是其他中介)。当反序列化的时候系统会去检测文件中的serialVersionUID
,看它是否和当前类的serialVersionUID
一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能会发生变化,这时候就无法正常的反序列化。
以一般来说,我们应该手动去指定serialVersionUID
的值,比如”1L”,也可以让IDE根据当前类的结构去生成对应的hash
值,这样序列化和反序列化时两者的serialVersionUID
是相同的,因此可以正常的进行反序列化。如果不不设置serialVersionUID
,系统在序列化的时候默认会根据类的结构在生成对应的serialVersionUID
,在反序列化的时候,如果当类有变化,比如增加或者减少字段,这时候当前的类的serialVersionUID
和序列化的时候的serialVersionUID
就不一样了,就会出现反序列化失败,如果没有捕获异常会导致crash
。
所以当我们手动指定了它之后,就可以很大程度上避免了反序列化过程的失败。
比如当版本升级以后,我们可能删除了某个成员变量也可能增加一些新的成员变量,这个时候我们的反序列化过程仍然可以成功,程序仍然能够最大限度地恢复数据。相反 如果我们没有指定serialVersionUID
的话,程序就会挂掉。
当然我们也要考虑到另外一种情况,如果类结构发生了非常规性的改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID
验证通过了,但是反序列化过程还是会失败,因为类的而结构有了重大改变,根本无法从老版本的数据还原出一个新的类结构对象。
注意
静态成员变量属于类,不属于对象,所以不会参与序列化的过程
用transient关键字编辑的成员变量不参与序列化的过程。
可以通过重写
writeObject
和readObject
两个方法来重写系统默认的序列化和反序列化的过程。不过并不推荐。
Serializable
和 Parcelable
的区别
1、平台区别
Serializable
是属于Java
自带的,表示一个对象可以转换成可存储或者可传输的状态,序列化后的对象可以在网络上进行传输,也可以存储到本地。Parcelable
是属于Android
专用。不过不同于Serializable
,Parcelable
实现的原理是将一个完整的对象进行分解。而分解后的每一部分都是 Intent 所支持的数据类型。
2、编写上的区别
Serializable
代码量少,写起来方便Parcelable
代码多一些,略复杂
3、选择的原则
- 如果是仅仅在内存中使用,比如
activity
、service
之间进行对象的传递,强烈推荐使用Parcelable
,因为Parcelable
比Serializable
性能高很多。因为Serializable
在序列化的时候会产生大量的临时变量, 从而引起频繁的GC
。 - 如果是持久化操作,推荐
Serializable
,虽然Serializable
效率比较低,但是还是要选择它,因为在外界有变化的情况下,Parcelable
不能很好的保存数据的持续性。
4、本质的区别
Serializable
的本质是使用了反射,序列化的过程比较慢,这种机制在序列化的时候会创建很多临时的对象,比引起频繁的GC、Parcelable
方式的本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent
所支持的类型,这样就实现了传递对象的功能了。
Java transient
解析
transient
关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient
关键字修饰的。变量如果是用户自定义的类变量,则该类是要实现Serializable
接口。- 一旦变量被
transient
修饰,变量将不是对象持久化的一部分,该变量内容在序列化后无法获得访问。 - 静态变量不管是否被
transient
修饰,都无法被序列化。
注意: 被 transient
修饰的变量也是可以被序列化的。
Java 中,对象的序列化可以通过实现两个接口实现:
- 若实现的是
Serializable
接口,则所有的序列化都将会自动进行,被transient
修饰的变量将不会被序列化。 - 若实现的是
Externalizable
接口,则任何东西都需要自己在writeExternal
方法中自己手动指定需要序列化的变量。任何变量都可以序列化,与变量是否被transient
修饰无关。
其他注意事项
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
- 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
- 并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了。比如:
- 安全方面的原因,比如一个对象拥有
private
,public
等field
,对于一个要传输的对象,比如写到文件,或者进行RMI
传输 等等,在序列化进行传输的过程中,这个对象的private
等域是不受保护的。 - 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
- 安全方面的原因,比如一个对象拥有