在面试中,Java 序列化被问到的几率还是挺高的。所以搜集了 Java 序列化常见的问题,由浅入深的帮助大家进一步学习和理解。
序列化基础知识什么是序列化?
Java 序列化是 JDK 1.1 中引入的特性之一。
总的来说,序列化讲一个 Java 对象所描述的所有内容以文件 IO 的方式 存储 或 传输 的过程。核心作用是对象状态的保存和重建。
在这里有两个比较重要的概念:
序列化:把 Java 对象转换为字节码的过程
反序列化:把字节码还原为 Java 对象的过程
为什么要序列化 ?
因为 Java 对象是存放在 JVM 的 堆内存 中的,当 JVM 退出的时候,对象也就随之销毁。如果想 持久化 或进行 网络传输 对象数据时,那就必须把对象转为计算机可以识别的字节码。
在以下场景中需要使用到序列化。
持久化数据:文件、数据库、缓存
网络传输:RMI (远程调用 Remote Method Invocation)、RPC
如何实现序列化
在 Java 中,没有关键字可以直接去定义一个所谓的 可持久化 对象。这就需要我们在代码中 显示地 进行序列化和反序列化还原操作。
Serializable 接口Serializable 接口是一个 标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。
1、定义
2、序列化
3、反序列化
4、结果
5、如果不实现 Serializable 接口将无法进行序列化或反序列化
Externalizable 接口Externalizable 继承了 Serializable 接口,还定义了两个抽象方法:writeExternal() 和 readExternal()。
如果开发人员使用 Externalizable 来实现序列化和反序列化,必须重写 writeExternal() 和 readExternal() 方法。
因为实现 Externalizable 接口之后,基于 Serializable 接口的默认化序列化机制就会失效。
Serializable 和 Externalizable 的区别 Serializable ExternalizableJava 支持比较完整,自动存储必要信息 需要开发人员自己完成
所有对象由 Java 统一保存,性能较低 开发人员决定哪个对象保存,可以提升速度
保存时占用空间大,性能差 部分存储,空间占用可能较少,性能相对高
Java 序列化协议分析
下面这段字节码是保存在本地的字节码文件,接下来准备对这段字节码进行 拆分 和 讲解 (只针对 Serializable)。
以下的字节码定义参考 java.io.ObjectStreamConstants 中的定义,如果有兴趣,找到这个类,里面有详细的定义。
JDk 序列化的魔数
aced STREAM_MAGIC 魔数,用于标识当前文件的头部
0005 STREAM_VERSION 序列化协议版本号
描述对象的类型信息
73 TC_OBJECT 表示序列化的是一个普通 Java 对象 (Object 0x73,String 0x74,Array 0x75)
72 TC_CLASSDESC 表示当前的对象的类型信息
0014 表示类名的长度,这段代码中是 0014 换算过来是 20 个字节
7374 6174 6963 4661 6374 6f72 792e 5065 7273 6f6e 表示类名,即 staticFactory.Person
0000 0000 0000 0001 类名后的 8 个字节是一个长整数,即 serialVersionUID = 1L
02 SC_SERIALIZABLE 标识位,说明这个类实现了 Serializable 接口。
对象的字段表
0002 表示这个对象中有 2 个属性
49 即 I 表示 int,说明这是一个 32 位整数
0003 表示属性名的长度,即 3 字节
6167 65 表示属性 age
4c 即 L,表示引用类型,说明这个属性是某个类型的引用
0004 表示属性名的长度,即 4 字节
6e61 6d65 表示属性 name
74 TC_STRING 表示后面是个字符串
0012 表示字符串长度,即 18 字节
4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b 即 Ljava/lang/String
父类的描述信息
78 TC_ENDBLOCKDATA 标志所有的字段类型信息描述结束
70 TC_NULL 代表 null,即没有父类
对象的属性值
0000 001e 初始化后的年龄,转换后即 30
74 TC_STRING 表示后面是个字符串
0005 表示字符串长度为 5
4865 6e72 79 初始化之后的姓名,转换后即 Henry
序列化的特性在实际应用中,有些时候 不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据。
本段重点讨论 transient 和 static 之间的区别,并讨论每个关键字的作用。
transient 关键字当我们的一个字段被声明为 transient 后,默认序列化机制就会忽略掉该字段的内容,不会被保存。
static 关键字序列化仅对特定的变量产生作用,但 static 修饰的变量并不特定于任何对象。因此,静态变量不会参与序列化。
虽然用关键字可以避免序列化,但是当关键字组合使用的时候,也可能会失效。
transient 和 static 的规则临时变量在序列化过程中将被忽略。
static 变量不会参与序列化。
如果在声明本身期间对值进行了初始化,则静态变量将被序列化。