🕓 序列化 & 反序列化

吞佛童子2022年10月10日
  • Java
  • 序列化
大约 6 分钟

🕓 序列化 & 反序列化

1. 作用

1) 持久化到磁盘

  • MyBatis 在持久化到数据库时,不是将对象类持久化到数据库,而是对象类的属性依次持久化到数据库中

2) 网络传输

  • 会将对象类转换为 JSON 字符串,而 String 实现了 Serializable 接口

2. Serializable 接口

// 起 标识作用
public interface Serializable {
}

1) 序列化 & 反序列化 为什么需要实现 Serializable 接口

  • 实现 Serializable 接口后,JVM 底层会帮我们将对象进行 序列化 & 反序列化
  • 若不实现 Serializable 接口,我们需要自己对对象进行 序列化 & 反序列化

2) serialVersionUID 的作用

  1. 虚拟机是否允许反序列化, 不仅取决于 类路径和功能代码 是否⼀致, 还取决于 serialVersionUID 是否⼀致
  2. 若没有显示指定 serialVersionUID,JVM 序列化时,会根据 属性 自动生成 serialVersionUID,然后与 属性 一起序列化
    • 在反序列化时,根据 属性 再次生成一个 serialVersionUID,然后根据原本的 serialVersionUID,和当前 serialVersionUID 比较,如果相同,表示序列化成功
    • 只有 Class 类不变,那么每次生成的 serialVersionUID 一致,而只要类对象发生改变,serialVersionUID 就会发生改变
  3. 若显示指定 serialVersionUID,JVM 序列化 & 反序列化生成的都是我们指定的 serialVersionUID
    • 在实际开发中,若我们修改了对象类,serialVersionUID 仍然不变,因此序列化 & 反序列化均不会抛出 InvalidCastException 异常
  4. 因此,我们在序列化类时,需要显式指定 serialVersionUID,它为 Long 类型数据结构

3) 简单实现序列化

4) 自定义序列化

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // transient 修饰,不会被序列化,反序列化得到的是 null
    
    // 1. 序列化
    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
        int expectedModCount = modCount;
        s.defaultWriteObject();

        s.writeInt(size);

        for (int i=0; i<size; i++) { // 只序列化有元素的数组部分
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    
    // 2. 反序列化
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        s.defaultReadObject();

        s.readInt(); // ignored

        if (size > 0) {
            int capacity = calculateCapacity(elementData, size); // 保证数组空间足够
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            for (int i=0; i<size; i++) { // 读取,序列化后的有元素部分的数组
                a[i] = s.readObject();
            }
        }
    }
}

5) 底层原理

writeObject()

readObject()

6) 补充

  1. transient 修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值,
    • int == 0,对象 == null
  2. static 属性不会被序列化
    • 序列化是针对 对象而言,而 static 属性变量是属于类的,优于实例对象的出现,随着类加载而存在,因此不会序列化
  3. private static final long serialVersionUID = 8683452581122892189L; 也被 static 修饰,为什么还会被序列化?
    • serialVersionUID 其实并未被序列化,而是在反序列化时,将我们指定的值赋值给 serialVersionUID,所以看起来像是被序列化了
  4. 类要想正确序列化,其字段属性也需要能被序列化,属性对象也需要实现 Serializable 接口
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou