序列化
序列化是将数据对象转换为可传输格式的过程。是一种数据持久化的手段,一般广泛应用于网络传输、RPC、RMI等场景中。
常见的序列化框架:
java | jdk自带,使用方便,可序列化所有类,速度慢,占空间 |
---|---|
protobuf | 速度快,但需要先进行静态编译 |
kryo | 速度快,序列化后体积小;跨语言支持较复杂 |
在java
的序列化机制中,需要进行序列化的对象需要实现Serializable
接口,这个接口没有方法或者字段,单纯是为了表示可序列化语义。自定义的类通过实现Serializable
接口,进而在IO
过程中实现序列化和反序列化。
在序列化过程中,会对将要序列化/方序列化的类进行检查,要求操作对象必须是字符串、枚举类、数组类型或者是实现了Serializable
的类。
创建一个继承于Serializable
接口的类,然后通过流处理将这个类写入到1.txt
中,注意,这里写入之后,如果直接打开文件,会发现这是个乱码文件,这是因为序列化写入的是二进制文件。
1 | public class Friend implements Serializable{ |
这里的序列化调用链:objectOutputStream.writeObject()
→writeObject0(obj, false);
→ writeOrdinaryObject(obj, desc, unshared);
(区分是Serializable
还是Externalizable
)→如果没有实现自定义的序列化策略则调用defaultWriteFields(obj, slotDesc);
,如果实现了自定义序列化策略,则通过反射调用到自定义序列化策略slotDesc.invokeWriteObject(obj, this);
。
但是需要注意,就算是自定义了序列化接口writeObject()
,且不进行写入动作,最终的输出文件依然不会是空文件,为了反序列化,包含了一些格式信息,不单纯只有数据,比如有字段类型等信息。
Externalizable的作用是什么?
它相比于Serializable
更为灵活,比如一个类有一个父类,在序列化时,如果也想序列化父类中的属性,则这个父类也需要实现Serializable
接口;另一种做法是是子类实现Externalizable
接口,但是实现该接口需要重写writeExternal()
和readExternal()
方法,这种情况下就可以指定需要序列化的属性。
1 | public class Friend2 implements Externalizable { |
serialVersionUID的作用?
一般一个类在继承Serializable
接口后,都会定义一个字段private static final long serialVersionUID = 1L;
,那么它的作用是什么?
它主要是用来校验是否允许进行反序列化的,当将某个类进行序列化后,比如是存储到文件中,会将这个值也序列化到文件中,当进行反序列化时,将这个serialVerionUID
进行比对,如果不一致,则抛出InvalidCastException
异常。
如果类发生了改变(比如新增字段、删除字段、改变字段类型等),这时如果进行反序列化,可能出现不匹配的情况,如果没有显示声明这个字段值,java
会生成一个默认的serialVersionUID
,这个值是基于类的结构生成的,当类的结构发生变化的时候,再次序列化时这个值也会发生变化,就会导致使用变更之前的数据进行反序列化会抛出异常。但是我们在使用时,通常为了确保即使类发生了变化,也要序列化成功,会显示的声明这个值。
数组的序列化
在序列化的过程中,如果被序列化对象自定义实现了writeObject()
和readObject()
方法,则会尝试使用自定义的方法进行序列化和方序列化。
在ArrayList
中,它的存储是transient Object[] elementData
,但是这个对象数组又被声明为transient
,所以在默认的序列化策略中,并不会对它进行序列化,这是因为ArrayList
是动态数组,每次在放满之后会进行动态的扩容,那么就会出现一个数组中有多个null
元素,为了保证null
元素不被序列化,所以将数组设置为transient
后,通过自定义序列化策略进行序列化。
1 | private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{ |