Java序列化与反序列化


《Java I/O》笔记

序列化会阻止垃圾回收
  每次对象被写入到一个对象输出流时,流会保存一个到对象的引用。然后,如果有相同的对象呗写入到相同的流,它就能被替换为对之前已写入的引用。但这样,对象会一直存活,阻止了垃圾回收,之后当流被reset或close之后,才能够被垃圾回收。

  close:对同一个文件,只能用对象输出流写入一次(不能追加写入),如果调用了close方法,然后再重新构造(追加模式),读取时会出错。
  reset:对同一个对象输出流,可以多次调用reset,此时,保存的引用被释放(也就可以垃圾回收),读取时和平常一样。但是如果写入同一个对象(之间调用了reset),则可能重复写入。

不可序列化的原因

Overall, there are seven common reasons why a class may not be serializable:

  1. It is too closely tied to native code (java.util.zip.Deflater).

  2. The object's state depends on the internals of the virtual machine or the runtime environment and thus may change from run to run (java.lang.Thread, java.io.InputStream, java.io.FileDescriptor, java.awt.PrintJob).

  3. Serializing it is a potential security risk (java.lang.SecurityManager, java.security.MessageDigest).

  4. The class is mostly a holder for static methods without any real internal state (java.beans.Beans, java.lang.Math).

  5. The class is a nonstatic inner class. Serialization just doesn't work well with nonstatic inner classes. (Static inner classes have no problem being serialized.)

  6. The programmer who wrote the class simply didn't think about serialization.

  7. An alternate serialization format is preferred in a particular context. (XOM node classes are not serializable because the proper serialization format for XML is XML.)


一些类实现了Serializable接口,但如果序列化会抛出NotSerializableException异常。
  • 类的实例域包含对非序列化对象的引用,并且在序列化时,会作用于这个引用。
  • 超类不可序列化,而且不包含public无参构造器(如果超类不可序列化,那么超类的信息会丢失
  • 故意抛出NotSerializableException异常。因为有时超类已经实现了Serializable,那么它也就实现了该接口,为了不可序列化,抛出异常。

序列化存储对象的状态和类的名字,但不存储类的字节码。因此,可能序列化和反序列化所参照的类时不同的。

默认情况下:

The following changes are compatible:

  • Most changes to constructors and methods, whether instance or static. Serialization doesn't touch the methods of a class. The exceptions are those methods directly involved in the serialization process, particularly writeObject( ) and readObject( ).

  • All changes to static fieldschanging their type, their names, adding or removing them, etc. Serialization ignores all static fields.

  • All changes to transient fieldschanging their type, their names, adding or removing them, etc. Serialization ignores all transient fields.

  • Adding or removing an interface (except the Serializable interface) from a class. Interfaces say nothing about the instance fields of a class.

  • Adding or removing inner classes.

  • Changing the access specifiers of a field. Serialization does not respect access protection.

  • Changing a field from static to nonstatic or transient to nontransient. This is the same as adding a field.

The following changes are incompatible and thus prevent deserialization of serialized objects:

  • Changing the name of a class.

  • Changing the type of an instance field.

  • Changing the superclass of a class. This may affect the inherited state of an object.

  • Changing the writeObject( ) or readObject( ) method (discussed later) in an incompatible fashion.

  • Changing a class from Serializable to Externalizable (discussed later) or Externalizable to Serializable.

Finally, adding, removing, or changing the name of a nontransient instance field is incompatible by default.

上面的compatible只是说明此类变化不会改变对象序列化的格式,但有可能改变默认的serialVersionUID

通过在新类中指定和旧类相同的SerialVersionUID,可以使新类兼容旧类。但你必须负责它们的兼容性。



自定义序列化过程
  • 声明某个实例域为transient,这样在(反)序列化时此域不会被(反)序列化。(只在默认序列化时有效(defaultWriteObject/defaultReadObject也有效))
  • 实现private readObject/writeObject方法,当序列化或反序列化时,会调用相应的这两个方法。但超类的的序列化使用标准的流程。
  • 扩展Externalizable而不是Serializable接口。当序列化/反序列化时,the virtual machine does almost nothing except identify the class。这时,超类的序列化也由你负责。
    • 若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中
    • 必须实现public writeExternal和readExternal方法以序列化
    • 在扩展该接口的情况下,transient,serialPersistentFields对其没有影响。readResolve,writeReplace对其仍有影响。当然,readObject和writeObject必然没影响。
    • 实现该接口必须负责版本控制。
  • 实现readResolve方法:如果类中实现了该方法, 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
  • 实现writeReplace方法:如果类中实现了该方法,对于返回的对象有两种情况:
    • 返回的是该类的对象:这是遵循普通的流程,把返回的对象按正常的方式序列化。
    • 返回的不是该类的对象(设为B):此时只会序列化返回的对象,按照序列化B的方式序列化,当反序列化时,会按照反序列化B的方式反序列化。也就是说,序列化返回的对象,和此类没有关系了(当然,readResolve方法也不会被调用)。
  • 声明private static final serialPersistentFields数组,在该数组中的域会被序列化。(它的优先级高于transient关键字
    • 通过在serialPersistentFields中声明不存在的域,以保证兼容。
    • 比如,定义了一个类Point,里面有域x,y坐标。
    • 之后,你修改这个类使用极坐标,有域radius,angle。
    • 这两个类分别被两个客户端使用,但任何一个版本序列化的数据,不能被另一个版本反序列化。
    • 为了兼容,可以在第二个版本的类中,使用serialPersistentFields声明第一个版本中要序列化的域,然后在writeObject方法中,转换radius,angle到相应的x,y坐标,并写入。在readObject方法中,得到x,y坐标,并转换为radius,angle。
      示例代码:
      private   static   final   ObjectStreamField []   serialPersistentFields    =   {
          new   ObjectStreamField   ( "x" ,   double .   class ),
          new   ObjectStreamField   ( "y" ,   double .   class ),
      };

      private   void   writeObject ( ObjectOutputStream out   )   throws   IOException   {
          // Convert to Cartesian coordinates
        ObjectOutputStream . PutField fields   =   out .   putFields (   );
        fields . put   ( "x" ,   radius   *   Math   . cos (   angle ));
        fields . put   ( "y" ,   radius   *   Math   . sin (   angle ));
        out . writeFields   (   );
      }

      private   void   readObject ( ObjectInputStream in   )
           throws   ClassNotFoundException   ,   IOException   {
        ObjectInputStream . GetField fields   =   in .   readFields (   );
          double   x   =   fields . get ( "x"   ,   0.0   );
          double   y   =   fields . get ( "y"   ,   0.0   );
               get方法根据第二个参数的类型来决定域的类型,所以,如果第二个参数是0,则会出错,因为没有类型为int的名为x的域。
          // Convert to polar coordinates
        radius   =   Math   . sqrt (   x * x   +   y   * y );
        angle   =   Math   . atan2 (   y ,   x );
      }
        

    • 这样,任何一个版本写入的数据,都能够被另一个版本读取使用。

解析Class
ObjectInputStream的readObject方法只从本地classpath中加载类,如果类找不到,则抛出异常。

可通过扩展ObjectOutputStream类并实现annotateClass方法以提供Class的信息(可以是类字节码或URL)。

并通过扩展ObjectInputStream类并实现resolveClass方法以提取Class的信息。(如果找不到相应类,则应抛出ClassNotFoundException异常)。

解析对象
仅仅可信任的ObjectInputStream/ObjectOutputStream子类可以替换对象。可信任的类表示它是从本地路径加载的。

ObjectOutputStream:
  为了可以替换对象,必须在其子类中调用protected的enableReplaceObject方法,并传递参数true。
  之后在读取对象之后,从writeObject返回之前调用replaceObject。

ObjectInputStream:
  为了可以替换对象,必须在其子类中调用protected的enableResolveObject方法,并传递参数true。
  之后在读取对象之后,从readObject返回之前调用resolveObject。
  对于读取的每个对象,都会调用该方法,也就是说,如果每个对象o中引用了其他的对象(a,b,c),每个对象都会调用该方法,可通过instanceof来区分不同的对象,并根据不同的类型采取不同的动作。(因为它是一个流中方法)

验证
通过调用ObjectInputStream的registerValidation方法,可注册对对象的验证类。在readObject方法返回之前,调用ObjectInputValidation的validateObject方法以验证。


你可能感兴趣的:(java,IO,序列化,反序列化)