##1.什么是序列化

序列化又称持久化,就是指把java对象转换为字节序列进而将其长久保存下来(如保存到文件)的过程。在Java程序运行的过程中,会在内存中生成一个或多个java对象,而这些对象在程序运行结束之后会消失,而如果我们想将其持久地保存下来,比如保存到磁盘的某个文件中,那么我们就需要序列化的技术,将对象转换为字节序列,保存到文件中。序列化机制使得对象可以脱离程序的运行而独立存在。

2.什么是反序列化

反序列化就是把序列化中保存的字节码文件转换回对象的过程。

3.如何进行序列化

一个类的对象要想序列化成功,必须满足两个条件:

  • 该类必须实现 java.io.Serializable 接口
  • 该类的所有属性必须是可序列化的。不想被序列化的属性应该用transient关键字修饰

序列化的最主要的两个步骤:

  • 创建一个ObjectOutputStream输出流;
  • 调用ObjectOutputStream对象的writeObject输出可序列化对象。

我们定义一个实现了Serializable接口的类:

public class Employee implements java.io.Serializable{
   public String name;
   public String address;
   public transient int SSN;
   public int number;
   
   public void mailCheck(){
      System.out.println("Mailing a check to " + name
                           + " " + address);
   }
}

然后再创建一个进行序列化操作的类:

import java.io.*;
 
public class SerializeDemo{
   public static void main(String [] args){
      Employee e = new Employee();
      e.name = "Reyan Ali";
      e.address = "Phokka Kuan, Ambehta Peer";
      e.SSN = 11122333;
      e.number = 101;
      
      try{
         FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(e);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in /tmp/employee.ser");
      }catch(IOException i){
          i.printStackTrace();
      }
   }
}

执行过后,Employee这个类的实例化对象e就被我们序列化到了一个名为employee.ser的对象中了。

4.如何进行反序列化

反序列化的步骤两个最主要的步骤:

  • 创建一个ObjectInputStream输入流;
  • 调用ObjectInputStream对象的readObject()得到序列化的对象。

我们定义一个用于反序列化Employee对象的类:

import java.io.*;
 
public class DeserializeDemo
{
   public static void main(String [] args)
   {
      Employee e = null;
      try
      {
         FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
         ObjectInputStream in = new ObjectInputStream(fileIn);
         e = (Employee) in.readObject();
         in.close();
         fileIn.close();
      }catch(IOException i)
      {
         i.printStackTrace();
         return;
      }catch(ClassNotFoundException c)
      {
         System.out.println("Employee class not found");
         c.printStackTrace();
         return;
      }
      System.out.println("Deserialized Employee...");
      System.out.println("Name: " + e.name);
      System.out.println("Address: " + e.address);
      System.out.println("SSN: " + e.SSN);
      System.out.println("Number: " + e.number);
    }
}

程序的运行结果如下:

Deserialized Employee...
Name: Reyan Ali
Address:Phokka Kuan, Ambehta Peer
SSN: 0
Number:101

可以看到,序列化之前该对象的属性(除了被transient关键字修饰的)都可以打印出值。

5.自定义序列化

通过重写writeObject与readObject方法,可以自己选择哪些属性需要序列化, 哪些属性不需要。如果writeObject使用某种规则序列化,则相应的readObject需要相反的规则反序列化,以便能正确反序列化出对象。这里展示对名字进行反转加密。

public class Person implements Serializable {   
    private String name;   
    private int age;   
    //省略构造方法,get及set方法 
    
    private void writeObject(ObjectOutputStream out) throws IOException {       
        //将名字反转写入二进制流       
        out.writeObject(new StringBuffer(this.name).reverse());       
        out.writeInt(age);   
    } 
    
    private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{       
        //将读出的字符串反转恢复回来       
        this.name = ((StringBuffer)ins.readObject()).reverse().toString();       
        this.age = ins.readInt();   
    }
}

6.强制自定义序列化:Externalizable

通过实现Externalizable接口,可以实现序列化的自定义,但是必须实现writeExternal、readExternal方法。虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。

7.序列化版本号

我们知道,反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化怎么保证升级前后的兼容性呢?java序列化提供了一个private static final long serialVersionUID的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。

序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

8.注意事项

  • 反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。
  • 如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的(实现Serializable接口);否则,会导致此类不能序列化。错误类型是:java.io.NotSerializableException
  • Java序列化同一对象,并不会将此对象序列化多次得到多个对象。
  • 有保存到磁盘的对象都有一个序列化编码号
  • 当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。如果此对象已经序列化过,则直接输出编号即可。
  • 如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保存序列化编号。
  • 反序列化时必须有序列化对象的class文件。
  • 议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。

参考

https://www.runoob.com/java/java-serialization.html

https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf