序列化算法一般会按步骤做如下事情:
1、将对象实例相关的类元数据输出。
2、递归地输出类的超类描述直到不再有超类。
3、类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
构造方法
①bout:用于写入一些类元数据还有对象中基本数据类型的值,在下面会分析。
②enableOverride :false 表示不支持重写序列化过程,如果为 true ,那么需要重写 writeObjectOverride 方法。这个一般不用管它。
③writeStreamHeader() 写入头信息
writeObject
① lookup 函数用于查找当前类的 ObjectStreamClass ,它是用于描述一个类的结构信息的,通过它就可以获取对象及其对象属性的相关信息,并且它内部持有该对象的父类的 ObjectStreamClass 实例。其内部大量使用了反射,读者可以去看看这个类的源码。下面看看它的构造函数
② 根据 obj 的类型去执行序列化操作,如果不符合序列化要求,那么会③位置抛出 NotSerializableException 异常。
③ writeOrdinaryObject
①写入类的元数据,TC_OBJECT. 声明这是一个新的对象,如果写入的是一个 String 类型的数据,那么就需要 TC_STRING 这个标识。
②writeClassDesc 方法主要作用就是自上而下(从父类写到子类,注意只会遍历那些实现了序列化接口的类)写入描述信息。该方法内部会不断的递归调用,我们只需要关系这个方法是写入描述信息就好了,读者可以查阅一下源码。
从这里可以知道,序列化过程需要额外的写入很多数据,例如描述信息,类数据等,因此序列化后占用的空间肯定会更大。
③ desc.isExternalizable() 判断需要序列化的对象是否实现了 Externalizable 接口,这个在上面已经演示过怎么使用的,在序列化过程就是在这个地方进行判断的。如果有,那么序列化的过程就会由程序员自己控制了哦,writeExternalData 方法会回调,在这里就可以愉快地编写需要序列化的数据拉。
Externalizable
④ writeSerialData
在没有实现 Externalizable 接口时,就执行这个方法
1、desc.getClassDataLayout 会返回 ObjectStreamClass.ClassDataSlot[] ,我们来看看 ClassDataSlot 类,可以看到它是封装了 ObjectStreamClass 而已,所以我们就简单的认为 ① 这一步就是用于返回序列化对象及其父类的 ClassDataSlot[] 数组,我们可以从 ClassDataSlot 中获取对应 ObjectStreamClass 描述信息。
2、开始遍历返回的数组,slotDesc 这个我们就简单将其看成对一个对象的描述吧。hasWriteObjectMethod 表示的是什么呢?这个其实就是你要序列化这个对象是否有 writeObject 这个 private 方法,注意哦,这个方法并不是任何接口的方法,而是我们手动写的,读者可以参考 ArrayList 代码,它内部就有这个方法。那么这个方法的作用是什么呢?这个方法我们在上面也演示过具体的使用,它就是用于自定义序列化过程的,读者可以返回到上面看看如果使用这个 writeObject 实现自定义序列化过程的。注意:其实这个过程不像实现 Externalizable 接口那样,自己完全去自定义序列化数据。
自定义 Object#writeObject (java.io.ObjectOutputStream)
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
//执行 JVM 默认的序列化操作
s.defaultWriteObject();
//手动序列化 arr 前面30个元素
for (int i = 0; i < 30; i++) {
s.writeObject(arr[i]);
}
}
3、defaultWriteFields 这个方法就是 JVM 自动帮我们序列化了,
备注
UUID 兼容性
当一个类的 UID 改变时,这个类的兼容性就会出现问题。
什么时候 UID 会改变呢?
- 手动去修改导致当前的 serialVersionUID 与序列化前的不一样。
- 我们根本就没有手动去写这个 serialVersionUID 常量,那么 JVM 内部会根据类结构去计算得到这个 serialVersionUID 值,在类结构发生改变时(属性增加,删除或者类型修改了)这种也是会导致 serialVersionUID 发生变化。
- 假如类结构没有发生改变,并且没有定义 serialVersionUID ,但是反序列和序列化操作的虚拟机不一样也可能导致计算出来的 serialVersionUID 不一样。