Java Tutorials-06-序列化

@toc:

  • 序列化 Example
  • Serializable 接口和 serialVersionUID
  • 类的哪些字段不会被序列化
  • 如何自定义序列化的策略?ArrayList 是如何序列化数组的?
  • JDK 序列化的实现

序列化 Example

class User implements java.io.Serializable {
private static final long serialVersionUID = 1L;
transient Logger logger = LoggerFactory.getLogger(Config.class);
long uid;
String nick;
}

// 写入
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(filePath));
os.writeObject(user);
os.close();

// 读取
ObjectInputStream is = new ObjectInputStream(new FileInputStream(filePath));
User newUser = (User)is.readObject();
is.close();

Serializable 接口和 serialVersionUID

  • 类必须实现自 Serializable 接口,才可以被 ObjectOutputStream & ObjectInputStream 序列化和反序列化,序列化时如果遇到未实现 Serializable 接口的类,会抛出 NotSerializableException 异常;

  • 建议:可序列化的类定义自己的 serialVersionUID: private static final long serialVersionUID,在进行兼容升级时保持不变,也可以通过改变序列化 ID 限制某些用户使用;

  • 在序列化时,VersionUID 被写入类对象的字节流,在反序列化时,ObjectInputStream 读取类对象字节流,并比较反序列化对象的 VersionUID 跟本地 class 的 VersionUID 作比较,如果不一致则抛出 InvalidClassException 异常;

  • 如果没有定义 serialVersionUID,ObjectStreamClass(序列化 & 反序列化操作的对象) 会自动生成一个,生成规则根据类名,接口名,属性名, 以及描述符等生成一个64位的哈希数字(见 ObjectStreamClass.getSerialVersionUID() 方法),每次改动类的代码都会导致自动生成的 serialVersionUID 发生变化;

类的哪些字段不会被序列化

  • static 成员
  • 被声明为 transient 的成员
  • 如果一个类有父类,那么父类中的成员如果也需要可序列化,那么父类也要实现 Serializable 接口

如何自定义序列化策略

如何自定义序列化策略?

  • 在序列化的类中实现 writeObject(ObjectOutputStream)readObject(ObjectInputStream) 方法即可

ArrayList 是如何序列化数组的?

  • ArrayList 内部是数组实现的,数据保存在 elementData[],但实际使用中数组的大部分位置都是空值,为了让空元素不会被序列化,ArrayList 把 elementData[] 声明为 transient,并实现了 writeObject & readObject 方法

JDK 序列化的实现

如果一个类中包含 writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?
Serializable 只是一个空接口,它是如何保证只有实现类才可以被序列化呢?

以 ObjectOutputStream 序列化为例,调用栈如下:

ObjectOutputStream.writeObject
writeObject0 // 判断 obj instanceof Serializable
writeClassDesc -> ... 通过 osc.getSerialVersionUID() 获取or生成 VersionUID
writeOrdinaryObject
writeSerialData
invokeWriteObject // 通过反射调用类的 writeObject(),如果没定义则执行默认方法