JDK序列化分析

1. Jdk序列化举例

1.1 JDK序列化简介

序列化是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。java序列化是指把java对象转换为字节序列的过程,而java反序列化是指把字节序列恢复为java对象的过程。

1.2 JDK序列化示例
public class Student implements Serializable {

    private String id;

    private String name;

    public Student() {
    }

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class JdkSerialize {

    public static void serializePerson(Student student) throws FileNotFoundException, IOException {
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("/Users/zhuqiuhui/Desktop/student.txt")));
        oo.writeObject(student);
        oo.close();
    }

    public static Student deserializePerson() throws IOException, Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/Users/zhuqiuhui/Desktop/student.txt")));
        Student student = (Student) ois.readObject();
        return student;
    }
}

public static void main(String[] args) {
  Student student = new Student();
  student.setId("123");
  student.setName("方辰");

  try {
    // 序列化
    JdkSerialize.serializePerson(student);

    // 反序列化
    Student serializeStudent = JdkSerialize.deserializePerson();
    System.out.println(serializeStudent.getName());
  } catch (Exception e) {
    e.printStackTrace();
  }

}
1.3 JDK序列化注意点
  • 序列化对象要实现Serializable接口

  • 若序列化对象增加字段,需要要序列化对象上增加固定的serialVersionUID。否则在反序列化过程中有可能报出以下错误:

在这里插入图片描述

(比如说上面示例,若Student对象序列化后,再修改Student类,然后再进行反序列化就会报上述异常)。

2. JDK序列化分析

2.1 JDK序列化为什么需要 serialVersionUID ?

Serializable接口中文档描述如下:

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

描述重点有两点:

  • 实现Serializable接口的对象推荐显示指定 serialVersionUID(私有),否则可能报InvalidClassExceptions。
2.2 JDK序列化与反序列化源码分析

先看对象的序列化的过程中生成serialVersionUID的代码,如下(serialVersionUID作为类描述符信息序列化为存储对象的):

// java.io.ObjectStreamClass对象中
void writeNonProxy(ObjectOutputStream out) throws IOException {
  out.writeUTF(name);    // 写入类名
  out.writeLong(getSerialVersionUID());   // 写入类的序列号

  byte flags = 0;   // 类的标记
  if (externalizable) {
    flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
    int protocol = out.getProtocolVersion();
    if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
      flags |= ObjectStreamConstants.SC_BLOCK_DATA;
    }
  } else if (serializable) {
    flags |= ObjectStreamConstants.SC_SERIALIZABLE;
  }
  if (hasWriteObjectData) {
    flags |= ObjectStreamConstants.SC_WRITE_METHOD;
  }
  if (isEnum) {
    flags |= ObjectStreamConstants.SC_ENUM;
  }
  out.writeByte(flags);  // 写入类的标记

  out.writeShort(fields.length);  // 写入对象的字段数量
  for (int i = 0; i < fields.length; i++) {
    ObjectStreamField f = fields[i];
    out.writeByte(f.getTypeCode());
    out.writeUTF(f.getName());
    if (!f.isPrimitive()) {
      out.writeTypeString(f.getTypeString());
    }
  }
}

/**
** 在创建ObjectStreamClass时,会从类实例中取出“serialVersionUID”属性的值设置到ObjectStreamClass的suid中
**/
public long getSerialVersionUID() {
  if (suid == null) {
    suid = AccessController.doPrivileged(
      new PrivilegedAction<Long>() {
        public Long run() {
          return computeDefaultSUID(cl);  // 没有设置会创建一个默认的serialVersionUID(对类名,接口名,方法和属性的名称、修饰符以及描述符的64位哈希值,另外static/transient类型成员变量、私有方法都不参与Hash值计算。)
        }
      }
    );
  }
  return suid.longValue();
}

再看反序列化过程,即java.io.ObjectStreamClass#initNonProxy方法,如下:

/**
** ObjectStreamClass model:被序列化的对象(如存储文件)生成的
** cl:根据model(上面对象)的className获取到的类的class文件
*/
void initNonProxy(ObjectStreamClass model,
                  Class<?> cl,
                  ClassNotFoundException resolveEx,
                  ObjectStreamClass superDesc) throws InvalidClassException {
  
  long suid = Long.valueOf(model.getSerialVersionUID()); // model从
  
  ObjectStreamClass osc = null;
  if (cl != null) {
    osc = lookup(cl, true);
    if (osc.isProxy) {
      throw new InvalidClassException(
        "cannot bind non-proxy descriptor to a proxy class");
    }
    if (model.isEnum != osc.isEnum) {
      throw new InvalidClassException(model.isEnum ?
                                      "cannot bind enum descriptor to a non-enum class" :
                                      "cannot bind non-enum descriptor to an enum class");
    }

    // suid是要被序列化的对象的serialVersionUID(如存储文件),ObjectStreamClass osc是cl生成的(从类文件找到的最新的)
    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());
    }

    if (!classNamesEqual(model.name, osc.name)) {
      throw new InvalidClassException(osc.name,
                                      "local class name incompatible with stream class " +
                                      "name \"" + model.name + "\"");
    }
    //.......
}

2.3 结论
  • 在序列化过程,若不指定serialVersionUID,jdk会根据序列化对象的类信息生成默认的serialVersionUID,同时设置到类的描述符中。
  • 在反序列化过程中,读取到的序列化对象流会和最新的类的serialVersionUID对比,若不相等,则抛出InvalidClassExceptions异常。
  • 在JDK序列化过程中尽量对序列化对象设置固定的serialVersionUID。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bboyzqh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值