序列化

序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

为什么要把Java对象序列化呢?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

要实现序列化,需要实现 java.io.Serializable 接口,这里采用的 outputinput 是相对于Java对象的:从Java对象写入流,就是output,从流写入Java对象就是 input

写入流

从Java对象变成byte[]数组,需要 ObjectOutputStream ,它负责把一个Java对象写入一个字节流:

  1. public class OutputStreamTest {
  2. public static void main(String[] args) {
  3. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  4. try(ObjectOutputStream output=new ObjectOutputStream(buffer)){
  5. output.writeInt(123);
  6. output.writeUTF("1234");
  7. output.writeObject(Double.valueOf(123.456));
  8. System.out.println(Arrays.toString(buffer.toByteArray()));
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }

ObjectOutputStream既可以写入基本类型,如intboolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object
因为写入Object时需要大量的类型信息,所以写入的内容很大。

当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个.ser 扩展名。

反序列化

ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:

  1. try (ObjectInputStream input = new ObjectInputStream(...)) {
  2. int n = input.readInt();
  3. String s = input.readUTF();
  4. Double d = (Double) input.readObject();
  5. }

反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

安全性

因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

小结

可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface);
反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需);
Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。

serialVersionUID

简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException

推荐阅读

  1. https://www.liaoxuefeng.com/wiki/1252599548343744/1298366845681698