Java 序列化的基本概念

答案参考自:

什么是序列化?

序列化: 把对象转化为可传输的字节序列过程称为序列化。

反序列化: 把字节序列还原为对象的过程称为反序列化。

为什么需要使用序列化和反序列化?

目的是为了对象可以跨平台存储,和进行网络传输

序列化的有哪些好处

对对象进行序列化操作,可以极大程度的方便传输。

什么情况下需要序列化

  1. 当你想把的内存中的对象保存到一个文件中或者数据库中时候。
  2. 当你想用序列化在网络上传送对象的时候。

什么是serialVersionUID

serialVersionUID是用来辅助序列化和反序列化的过程。原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID一致才能成功的反序列化。

为什么还要显示指定serialVersionUID的值?

serialVersionUID的详细工作机制是这样的:

序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介)。当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能会发生变化,这时候就无法正常的反序列化。

以一般来说,我们应该手动去指定serialVersionUID的值,比如”1L”,也可以让IDE根据当前类的结构去生成对应的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常的进行反序列化。如果不不设置serialVersionUID,系统在序列化的时候默认会根据类的结构在生成对应的serialVersionUID,在反序列化的时候,如果当类有变化,比如增加或者减少字段,这时候当前的类的serialVersionUID和序列化的时候的serialVersionUID就不一样了,就会出现反序列化失败,如果没有捕获异常会导致crash

所以当我们手动指定了它之后,就可以很大程度上避免了反序列化过程的失败。

比如当版本升级以后,我们可能删除了某个成员变量也可能增加一些新的成员变量,这个时候我们的反序列化过程仍然可以成功,程序仍然能够最大限度地恢复数据。相反 如果我们没有指定serialVersionUID的话,程序就会挂掉。

当然我们也要考虑到另外一种情况,如果类结构发生了非常规性的改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类的而结构有了重大改变,根本无法从老版本的数据还原出一个新的类结构对象。

注意

  1. 静态成员变量属于类,不属于对象,所以不会参与序列化的过程

  2. 用transient关键字编辑的成员变量不参与序列化的过程。

  3. 可以通过重写writeObjectreadObject两个方法来重写系统默认的序列化和反序列化的过程。不过并不推荐。

SerializableParcelable 的区别

1、平台区别

  • Serializable是属于 Java 自带的,表示一个对象可以转换成可存储或者可传输的状态,序列化后的对象可以在网络上进行传输,也可以存储到本地。
  • Parcelable 是属于 Android 专用。不过不同于SerializableParcelable实现的原理是将一个完整的对象进行分解。而分解后的每一部分都是 Intent 所支持的数据类型。

2、编写上的区别

  • Serializable代码量少,写起来方便
  • Parcelable代码多一些,略复杂

3、选择的原则

  • 如果是仅仅在内存中使用,比如activityservice之间进行对象的传递,强烈推荐使用Parcelable,因为ParcelableSerializable性能高很多。因为Serializable在序列化的时候会产生大量的临时变量, 从而引起频繁的GC
  • 如果是持久化操作,推荐Serializable,虽然Serializable效率比较低,但是还是要选择它,因为在外界有变化的情况下,Parcelable不能很好的保存数据的持续性。

4、本质的区别

  • Serializable的本质是使用了反射,序列化的过程比较慢,这种机制在序列化的时候会创建很多临时的对象,比引起频繁的GC、
  • Parcelable方式的本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了。

Java transient 解析

  1. transient 关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被 transient 关键字修饰的。变量如果是用户自定义的类变量,则该类是要实现 Serializable 接口。
  2. 一旦变量被 transient 修饰,变量将不是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  3. 静态变量不管是否被 transient 修饰,都无法被序列化。

注意: 被 transient 修饰的变量也是可以被序列化的。

Java 中,对象的序列化可以通过实现两个接口实现:

  • 若实现的是 Serializable 接口,则所有的序列化都将会自动进行,被 transient 修饰的变量将不会被序列化。
  • 若实现的是 Externalizable 接口,则任何东西都需要自己在 writeExternal 方法中自己手动指定需要序列化的变量。任何变量都可以序列化,与变量是否被 transient 修饰无关。

其他注意事项

  1. 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  2. 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  3. 并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了。比如:
    1. 安全方面的原因,比如一个对象拥有privatepublicfield,对于一个要传输的对象,比如写到文件,或者进行RMI传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
    2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。

Java 序列化的基本概念
https://luoyuy.top/posts/376f5d8c1bd7/
作者
LuoYu-Ying
发布于
2023年3月6日
许可协议