Java序列化与反序列化

  1. 序列化

    • 概念:

      将Java中的对象转换为字节序列

    • 使用场景:

      如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储);

      在网络中传输对象

    • 实现方式:

      实现java.io.Serializable接口

    • 注意点:

      基本类型、数组、枚举等也会进行序列化

      transient修饰的属性和静态属性不参与序列化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      and the values of the non-transient
      and non-static fields of the class and all of its supertypes are
      written.

      if (obj instanceof String) {
      writeString((String) obj, unshared);
      } else if (cl.isArray()) {
      writeArray(obj, desc, unshared);
      } else if (obj instanceof Enum) {
      writeEnum((Enum<?>) obj, desc, unshared);
      } else if (obj instanceof Serializable) {
      writeOrdinaryObject(obj, desc, unshared);
      } else {
      if (extendedDebugInfo) {
      throw new NotSerializableException(
      cl.getName() + "\n" + debugInfoStack.toString());
      } else {
      throw new NotSerializableException(cl.getName());
      }
      }

      private void writeArray(xxx) {
      if (ccl.isPrimitive()) {
      if (ccl == Integer.TYPE) {
      int[] ia = (int[]) array;
      bout.writeInt(ia.length);
      bout.writeInts(ia, 0, ia.length);
      } else if (ccl == Byte.TYPE) {
      byte[] ba = (byte[]) array;
      bout.writeInt(ba.length);
      bout.write(ba, 0, ba.length, true);
      } else if (ccl == Long.TYPE) {
      ......
      }
      ......
      }

      }
    • 代码实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      class Student implements Serializable {
      private String name;
      private int number;
      // 被transient标记的属性不参与序列化,创建对象时赋值18,序列化后重新读取该属性的值为0
      private transient int age;
      // 静态属性不参与序列化,序列化后该属性值为null
      private static String gender;

      public void showStudent() {
      System.out.println("name: " + name);
      System.out.println("number: " + number);
      System.out.println("age: " + age);
      }

      .......省略getter、setter方法
      }

      @Test
      public void serializable() {
      // 创建对象
      Student student = new Student();
      student.setName("小花");
      student.setNumber(36);
      student.setAge(18);
      student.setGender("female");
      String greeting = "HelloWorld";

      // 创建FileOutputStream
      // 创建ObjectOutputStream
      try (FileOutputStream fos = new FileOutputStream("D:\\MyWork\\student.ser");
      ObjectOutputStream oos = new ObjectOutputStream(fos)) {
      // 将对象写入到文件
      oos.writeObject(student);
      oos.writeObject(greeting);
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
  2. 反序列化

    • 概念:

      将字节序列转换为Java对象

    • 使用场景

      如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储)

      在网络中传输对象,接收字节序列后重新读取对象信息

    • 注意点:

      必须确保该读取程序的CLASSPATH中包含有序列化类的class文件

    • 代码实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      @Test
      public void deserializable() {
      Student student = null;
      String greeting = "";
      // 创建FileInputStream
      // 创建ObjectInputStream
      try (FileInputStream fis = new FileInputStream("D:\\MyWork\\student.ser");
      ObjectInputStream ois = new ObjectInputStream(fis);) {
      // 从文件读取对象
      student = (Student) ois.readObject();
      greeting = (String) ois.readObject();
      } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
      }

      // 使用对象
      if (null != student) {
      student.showStudent();
      }
      System.out.println(greeting);
      }

      // 输出结果
      name: 小花
      number: 36
      age: 0
      gender: null
      HelloWorld
  3. serialVersionUID

    • 概念:

      序列化版本号,用来标识当前实现序列化接口的类,如果不显式指定,编译器会给类自动生成一个serialVersionUID

    • 作用:

      先看一个场景,在上述(1)中,Student类定义了name、number、age、gender四个属性,然后我们创建一个student对象并将其序列化保存到student.ser文件中,然后我们在Student类中增加一个属性,如className,再通过反序列化读取student.ser文件中的字节信息,转换成student对象,此时程序会报错

      1
      java.io.InvalidClassException: com.easyfun.base.Student; local class incompatible: stream classdesc serialVersionUID = -6313947761321746093, local class serialVersionUID = 4891437043098475710

      根据异常信息找到相应的代码:java.io.ObjectStreamClass#initNonProxy,当原class的序列化版本号和当前class的序列化版本号不一致会导致反序列化失败

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      void initNonProxy(xxx){
      long suid = Long.valueOf(model.getSerialVersionUID());
      ......
      if (model.serializable == osc.serializable &&
      !cl.isArray() &&
      suid != osc.getSerialVersionUID()) {
      throw new InvalidClassException(osc.name,
      "local class incompatible: " +
      "stream classdesc serialVersionUID = " + suid +
      ", local class serialVersionUID = " +
      osc.getSerialVersionUID());
      }
      ......
      }

      现在我们在Student类中添加上serialVersionUID(可以通过Idea自动生成,方法可自行搜索),然后调用序列化方法后,在Student类增加一个属性className,然后再调用反序列化方法,发现此次序列化不会再报错了

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      private static final long serialVersionUID = -6313947761321746093L;


      // 输出结果
      name: 小花
      number: 36
      age: 0
      gender: null
      className: null
      HelloWorld

      因此serialVersionUID的作用就比较好理解了,我们可以显式去指定某个类的序列化版本号,这样我们在反序列化时,即使该类的结构发生了变化,也能保证反序列化能够正常进行