05月19, 2020

Java - 序列化与反序列化

简介

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

序列化

在Java中,欲序列化的类需要实现Serializable/Externalizable接口。

序列化使用ObjectOutputStream.writeObject()方法

public static void Serialize(Object o){
    try{
        FileOutputStream fileOut =
                    new FileOutputStream("out/test.ser");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(o);
        out.close();
        fileOut.close();
    }catch(IOException i){
        i.printStackTrace();
    }
}

Serializable

对于实现Serializable接口的类来说,只有当它的所有属性都是可序列化的,或不可序列化的属性都是transient的时,它才可被序列化。如下面的Test类在序列化的时候就会报错:

public class Test implements Serializable{
   private final String name;
   private final Unserializable uns;
   public Test(String name, Unserializable uns){
      this.name = name;
      this.uns = uns;
   }
}
public class Unserializable {
    private final String name;
    public Unserializable(String name){
        this.name = name;
    }
}

P01.png

但若把Test类中uns属性的private final变为private final transient,Test类就可以正常序列化。

transient属性

Serializable接口实现序列化的方式是将类中的所有属性均序列化,而当我们不想将其中的某个属性序列化时,可以在该属性用transient修饰。

public class Test implements Serializable{
   private final String name;
   private final transient Unserializable uns;
   public Test(String name, Unserializable uns){
      this.name = name;
      this.uns = uns;
   }
}

Externalizable

该接口继承自Serializable接口,需要重写其中的writeExternal()readExternal()方法。通过这两个方法可以自定义欲序列化/反序列化的属性。

以下面的Test类为例,在writeExternal方法中可以自由指定要序列化的属性,以及它们的顺序(在readExternal方法中的顺序需相同)。

以这种方法实现序列化的类无法将欲序列化的属性设置为final

public class Test implements Externalizable{
   private String name;
   private int ID;
   private float age;
   private final Unserializable uns;

   public Test(){}

   public Test(String name, int ID, float age, Unserializable uns){
      this.name = name;
      this.ID = ID;
      this.age = age;
      this.uns = uns;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
      out.writeObject(name);
      out.writeObject(ID);
      out.writeObject(age);
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
      this.name = (String) in.readObject();
      this.ID = (int) in.readObject();
      this.age = (float) in.readObject();
   }
}

实现Externalizable接口的类必须提供一个public的无参构造器,否则会抛出一个InvalidClassException异常。

P02.png

  • 静态变量不会被序列化
  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口

反序列化

Java中,类的反序列化可以使用ObjectInputStream.readObject()方法实现。

下面的方法实现了反序列化,输入文件路径,返回一个对象。

public static Object Deserialize(String filepath){
    try{
        FileInputStream fileIn = new FileInputStream(filepath);
        ObjectInputStream in = new ObjectInputStream(fileIn);
        Object o=null;
        o = in.readObject();
        in.close();
        fileIn.close();
        return o;
    }catch(IOException i){
        i.printStackTrace();
        return null;
    }catch(ClassNotFoundException c){
        System.out.println("Class not found");
        c.printStackTrace();
        return null;
    }
}

对于transient修饰的属性,因未被序列化,故在反序列化时并不会改变它的值。若有初始化,则保持不变;若无初始化,则为空指针null

版本控制

一个类在经过序列化之后,其内部组成可能有改变,这时就可以使用一个静态变量来控制兼容性问题。若改动较小,希望兼容老版本,则版本不变;若改动较大,无法兼容老版本,则更新版本号。

private static final long serialVersionUID = 1L;

如使用下面的这个类进行序列化

public class Test implements Serializable{
   private static final long serialVersionUID = 1L;
   private final String name;
   private final int ID;
   private final float age;
}

修改后,更改了属性类型及版本号:

public class Test implements Serializable{
   private static final long serialVersionUID = 2L;
   private final StringBuilder name;
   private final long ID;
   private final double age;
}

反序列化时就会报错:

java.io.InvalidClassException: Test; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

P03.png

通过序列化实现深度克隆

Java的深拷贝可由序列化与反序列化完成。

如以下方法,接受一个实例(可序列化的),返回一个深度克隆的实例。

public static <T extends Serializable> T Clone(T object){
   T clone = null;
   try{
       ByteArrayOutputStream out = new ByteArrayOutputStream();
       ObjectOutputStream objout = new ObjectOutputStream(out);
       objout.writeObject(object);
       objout.close();

       ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
       ObjectInputStream objin = new ObjectInputStream(in);

       clone = (T) objin.readObject();
       objin.close();
   }catch (Exception e){
        e.printStackTrace();
   }
   return clone;
}

本文链接:http://blog.zireaels.com/post/Java-Serialize.html

-- EOF --

Comments