对象序列化(java)

首先望文生义的看,序列化指可以在网络上传输。但是仔细想的话,我自己的理解就像生活中的搬家一样,房子里有一个对象比如说是大衣柜,你要搬家了那这个衣柜怎么办呢,要拆开然后一块一块的去搬出去,实际上网络中类比这个可以理解成这样。

---------------------分割线(自此以上本人自己的理解,自此以下的来自于java疯狂讲义)------------------------------------------------------

   对象序列化的目标是把对象保存到磁盘中 ,或允许在网络中进行直接传输对象,对象序列化中的机制允许吧内存中的java对象转化成与平台无关的二进制流,然后持久的保存在磁盘上,其他程序一旦获得了这中二进制流,就可以恢复成原来的java对象,序列化机制可以使对象脱离程序而独立存在,对象的反序列化(Deserialize)是指将IO流恢复成对象。为了让类是可序列化的需要使类实现1.serializable 或者2.Externalizable,如果实现了1,里面无任何方法他只是表明该类的实例是可序列化的,

               ①反序列化的是java对象的数据而不是java类,所以需要提供java对象类所对应的.class文件,否则会引发classnotfondException。

               ②第二个需要注意的是,反序列化时不需要构造器来初始化java对象。

               ③使用序列化机制向文件中写入了多个java对象进行反序列化时必须按写入的顺序读取

               ④如果被序列化的类有多个父类时(直接或间接),需要父类要么实现Serializable接口要么增加无参的构造方法,但是如果只增加无参的构造方法会使该父类中的Filed值不会序列化到二进制流中

               ⑤对象引用的序列化如果一个Teacher类中有一个Person类的引用则Persion类必须是可序列化的否则无论Teacher类实现Serializable还是Externalizable接口 Teacher类都是不可序列化的(因为当反序列化时为了保证Teacher的正常恢复,也会序列化Person)

               ⑥Person ps=new Person("悟空a",345);Teacher t1=new Teacher("abk",ps);Teacher t2=new Teacher("hello_world",ps);这样会产生一个问题因为ps引用的是Person(只有一个),而在序列化传输时,如果将这三个分别传输过去的话,如果分别都去序列化一遍Person这个类,当进行反序列化时将不知道ps的是同一个类,江认为t1和t2是两个不同的对象,造成与序列化之前的结果不同,所以java序列化机制采用了特殊的序列化算法,当进行第一次序列化时会将Person序列化一遍,当对t1序列化时,所有保存到磁盘的对象都有一个自己的序列号,当他视图去序列化一个对象时会分析该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)序列化过才会被系统转换成字节序列输出,如果该对象已经被序列化过,程序只是输出一个序列化编号。

            ⑦但是这样会存在图个问题,就是只能第一次保持对象的实时更新,即:当第二次序列化时候之前这个对象的某个属性改变了则系统同样会输出第一次输出的对象

           ⑧Java提供了一种自定义序列化机制,如何序列化指定属性、甚至不序列化指定属性(与transient差不多),在序列化和反序列化的过程中,需要特殊处理的类需要实现以下的签名,用以实现自定义序列化


上面中用黑体自标出的是自定义的序列化,对于这个Person类而言序列化与反序列化该实例化的类没有区别,区别在于序列化后的对象流,即使使用例如Cracker截获后也是加密的name值,⑨还有一种更彻底的序列化机制,甚至可以改变序列化的对象,需要为该序列化类提供如下方法,




             上面第一句用到oos.writeObject()写入了一个Person对象,但第二行粗体代码读入的是ArrayList对象,因为Person类里的writeReplace()方法反回了一个ArrayList对象,可知,程序在序列化一个对象时总是先调用writeReplace()方法,如果该方法返回另一个对象则调用另一个对象的writeReplace()方法·····直到该方法不在返回另一个对象为止,程序最后将调用该对象的writeObject方法来保存该对象的状态,与writeReplace()相对的是,序列化机制里还有一个特殊的方法,可以实现保护性复制整个对象:ANY-ACCESS-MODIFIRE object ReadResolve() throws ObjectStreamException,该方法紧接着readObject()方法执行,该方法的返回值将会替代被序列化的对象,而原先被序列化的对象会被立即丢弃⑩readResolve()方法在序列化枚举类、单例类 是尤其有用,当然java5以后的枚举类都是没有任何问题的,如果有早期遗留下来的枚举类


                序列化机制可以用来克隆对象,这种情况下我们可以通过为Orientation提供一个readResolve()方法来解决,readReslove方法的返回值将会代替之前反序列化的对象,

         通过readResolve()方法得到的反序列化对象仍然是Orientation的HORIZONTAL或VERTICAL两个枚举值之一。所有的枚举类单例类在实现序列化时候都应该提供readResolve()方法,这样才可以保证反序列化的对象的正常,与writeReplace()方法对应的readResolve()方法的访问控制符是可以随意控制的,这样会有一个潜在的问题,例如当一个父类提供了readResolve()方法时,并且访问控制符是protected、public,则该类的子类在实现反序列化没有重写该readResolve()方法时实际得到的是父类的对象,但是每次都要重写好比也是一种冗余,通常的建议是final类如何写是没有问题的,其他类提供readResolve()方法时建议访问修饰符改为private.

================(累了吧,休息片刻继续学习吧)======================================================

                 ⒒另一种自定义序列化机制,这种序列化方式完全由程序员决定存储和恢复对象数据,需要实现Externalizable接口,该接口定义了如下的俩个方法:void readExternal(ObjectInput in)实现反序列化,调用接口DataInput(它是ObjectInput的父接口)下的的方法来恢复基本类型的Filed值,调用ObjectInput的readObject()方法来恢复引用类型的Filed值;void writeExternal(ObjectOutput out) 实现序列化,调用DataOutput(它是ObjectOutput的父接口)的方法来实现存储基本类型的Filed值,调用ObjectOutput下的writeObject()方法来存储引用类型的Filed值,实现Externalizable接口和上面的实现自定义序列化机制非常相似,只不过这个接口是强制实现自定义序列化。

           如果程序员要实现传输序列化对象的Externalizable,一样调用ObjectOutputStream()的writeObject()的方法输出该对象即可,反序列化对象调用ObjectInputStream()的readObject()方法

           虽然也可以用Externalizable可以带来性能的一定提升但是这样会比较复杂,所以大部分都是用Serializable来实现序列化。

关于对象序列化还需要注意一下几点:》对象的类名、Field值(包括基本类型、数组、其他对象的引用)都会被序列化,方法、静态属性、traisent(瞬态Field)都不会被序列化;

                                                             》实现序列化后,阻止某个Field被序列化,可以加transient,而不能加static,虽然static也可以不被序列化,但是static关键字不能这样用;

                                                            》保证被序列化对象的Field的类型是可序列化的,否则要加上transient关键字,不然该类不能被序列化;

                                                            》反序列化对象时候必须要有序列化对象时候的class文件;

                                                            》当通过文件、网络读取序列化对象时候需要按照实际写入顺序进行反序列化;

===================================版本问题======================================================

            反序列化时候需要有class文件呢,但是当class文件升级时候如何处理?java序列化机制允许为序列化类提供一个private static final 的serialVersionUID值,该Field值用于标识java类的序列化版本

            为了确保反序列化时候序列化的版本兼容问题,最好在每个序列化的对象中加入private static final long SerialVersionUID 这个Field值,数值自己定义,这样可以确保在被序列化后,该类被修改了,该对象也可以被正确的反序列化,如果没有显示的定义private static final long SerialVersionUID则该版本会被JVM根据类的相关信息计算

           不显示的定义seriaVersionUID也不利于不同JVM之间的移植,如果对类的修改会导致不能被反序列化,则应该为该类重新分配一个serialVersionUID;以下3种情况会导致反序列化失败

     》类修改了方法,不会影响反序列化,无需重新分配serialVersionUID

     》类修改了static Field值或瞬态Field值,则反序列化不受任何影响

     》类修改了非static Field值、非瞬态Field值,则可能导致序列化版本不兼容;

     》如果对象流中包含的Field值和新类中的Field值名称相同而类型不同则反序列化失败,serialVersionUID应更新

    》如果对象流中包含的Field值的个数多余新类中的Field的个数,则多余的会被忽略,版本可以兼容;

    》如果对象流中包含的Field值的个数少于新类中的Field的个数,版本可以兼容,但是新类中多余流中的属性会被至null(引用)、0(基本类型)。

猜你喜欢

转载自www.cnblogs.com/my-liu/p/9835994.html