对象序列化

        对象序列化的目标是将对象保存在磁盘中,或者在网络中传输对象,对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而把这二进制流保存在磁盘中或在网络中传输,而其它程序获取这种二进制流来恢复原来的Java对象,即序列化机制使得对象能够脱离程序的运行而独立存在。

        需要序列化的对象,必须让它的类是可序列化的,即该类需要实现如下的两个接口:

        Serializable

        Externalizable


一、使用对象流实现序列化

        让需要序列化的类实现Serializable标记接口即可,无需实现任何方法。


对象序列化_第1张图片
举例People类

        然后我们将可序列化的People类对象写入磁盘,再反序列化读取People类对象。


对象序列化_第2张图片
序列化和反序列化

注意:

        采用反序列化恢复对象的时候,我们必须要有此对象所属类的.class文件,否则会引发ClassNotFoundException异常。

        使用反序列化读取对象时,反序列化机制无需使用构造器来初始化对象。

        使用反序列化读取多个对象的时候,必须要按照写入对象的顺序来读取。



二、对象引用序列化

        当一个类A实现SerializableExternalizable接口时,类中的某个属性是引用类型的话,如果该引用类型的类不是可序列化的话,那类A依然不能够序列化,哪怕是实现了Serializable和Externalizable接口。(原因是:当序列化一个类A时,若类A中含有某个类对象的引用的属性时,为了反序列化可以正常恢复类A对象,则也会序列化该对象,所以该引用类型的类必须也是可序列化的,而且此种情况递归进行)


对象序列化_第3张图片

        Java中序列化机制采用的特殊的序列化算法:

            1.所有保存在磁盘中的对象都有一个序列化编号。

            2.当程序试图序列化一个对象的时候,会先检查在本次虚拟机中该对象是否未被序列化过,没有被序列化过程序才会将对象转化成字节列然后输出。

            3.如果对象已经是被序列化过,那么程序将是输出一个是序列化编号,而不是重新序列化该对象。

简单的来说,程序多次调用writeObject输出同一个对象时,只有第一次调用writeObject才会将对象转换成字节并输出。

对象序列化_第4张图片
例子WriteTeacher


对象序列化_第5张图片
序列化机制图(按照上面的例子)


对象序列化_第6张图片
验证序列化机制


验证结果


        还有一点要提的是对于同一个对象,由于只有第一次调用writeObject方法时才将对象转化成字节输出,之后的调用只会输出序列化编号,所以当在第一次调用writeObject方法后修改对象属性时,再次调用writeObject方法输出对象,结果是对象属性并不会做出改变!


对象序列化_第7张图片
序列化后改变对象属性


输出结果 



三、自定义序列化


1.使用transient关键字

          当我们序列化对象的时候,如果对象中的某个属性属于敏感信息,不想被序列化,那我们就需要使用transient关键字修饰属性(只能修饰属性)称为瞬态属性:


对象序列化_第8张图片
修改后的PeopleTransient类


对象序列化_第9张图片
测试类


输出结果

我们可以看到,年龄输出为0,并未输出真实年龄,说明年龄未被序列化。同时,静态属性(static修饰的属性)也不会被序列化!

2.使用特殊方法实现序列化

        →private void writeObject(java.io.ObjectOutputStream out) throws IOException:

            负责写入特定类的实例状态,以便相应的readObject恢复它。即我们可以自主决定哪些属性需要序列化,做怎样的处理。

        →private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException:

            负责从流中读取并恢复对象。即可以自主决定反序列化哪些属性,同时反序列化要和writeObject吃力保持一致。

        →private void readObjectNoData() throws ObjectStreamException:

            当序列化流不完整的时候,可以使用此方法正确的初始化反序列化的对象。(例如接受方使用的反序列化类的版本不同于发送方版本,或者接受方扩展的类不同与发送方扩展的类,亦或者序列化流被篡改过)


对象序列化_第10张图片
People类-修改版

        我们可以看到,我们向可序列化类中提供了2个方法,并且在writeObject方法中将name属性进行了包装,然后将字符串反转后输出,当然在readObject方法中同样将获取的数据包装后,对字符串进行反转后赋值给name属性。(所以在数据传输过程中,即使数据被截取,截取的也是我们操作后的数据,我们可以对其进行加密,所以很安全)

        接下来的序列化和反序列化操作思路和步骤与前面一致。

3.更彻底的序列化实现方法

        序列化的时候,将该对象替换成其它对象,序列化的类需要提供如下的方法:

        →ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectException:此方法将由序列化调用,前提此方法存在。(应为此方法拥有私有(private)、受保护(protected)、包私有(package-private)等权限,所以其子类可能获得该方法)


对象序列化_第11张图片
People类



对象序列化_第12张图片
ReplaceTest测试类



4.使用Externalizable实现自定义序列化

        使用Externalizable来时对象序列化,需要实现此接口的两个方法:

        →void writeExternal(ObjectOutput out):重写这个方法实现自定义序列化。改方法通过调用DataOutput(为ObjectOutput的父接口)的方法来保存基本类型的属性值,调用writeObject来保存引用类型的属性值。

        →void readExternal(ObjectInput in):重写这个方法来恢复对象。 该方法通过DataInput(为ObjectInput的父接口)的方法来恢复基本类型的属性值,调用readObject来恢复应用类型的属性值。


对象序列化_第13张图片
People类

注意,当通过实现Externalizable接口的时候,类中需要定义无参的构造函数。


对象序列化_第14张图片
ExternalizableTest类


四、两种序列化对比


对象序列化_第15张图片

对于对象序列化的一些注意事项:

        →对象的类名、属性(基本类型、数组和其它对象的引用)都会被序列化,瞬态属性(transient修饰)、静态属性(static修饰)、方法都不会被序列化。

        →对于实现Serializable接口的类,要哪些属性不被序列化,需使用transient关键字,不要使用static关键字,虽然static修饰的属性也不会被序列化。

        →对于序列化对象的属性的类型也必须是可序列化的,否则需要使用transient关键字修饰,不然无法完成序列化。

        →反序列化需要有.class文件。

        →反序列化需要按照序列化时写入的顺序来进行读取。

你可能感兴趣的:(对象序列化)