Java序列化
Java 序列化是一种将对象转换为字节流的过程,以便可以将对象保存到磁盘上,将其传输到网络上,或者将其存储在内存中,以后再进行反序列化,将字节流重新转换为对象。
要使一个类可序列化,需要让该类实现 java.io.Serializable
接口。
简单的实现:
public static void main(String[] args) throws IOException, ClassNotFoundException {
output();
System.out.println();
input();
}
public static void output() throws IOException {
A a = new A();
a.x = 100;
a.b = new B();
System.out.println(a); // org.example.serial.A@1b6d3586
System.out.println(a.x); // 100
System.out.println(a.b); // org.example.serial.B@4554617c
FileOutputStream fos = new FileOutputStream("A.ser");
ObjectOutput output = new ObjectOutputStream(fos);
output.writeObject(a);
output.close();
fos.close();
}
public static void input() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("A.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
A a = (A) ois.readObject();
System.out.println(a); // org.example.serial.A@568db2f2
System.out.println(a.x); // 100
System.out.println(a.b); // null
fis.close();
ois.close();
}
class A implements Serializable {
private static final long serialVersionUID = 1L;
int x;
transient B b;
}
class B implements Serializable {
}
其中transient
代表转瞬即逝的,被其修饰的变量值不会被发送到输出流,所以反序列化后的属性为对应的0值
。
版本号
在反序列化读取或者是序列化传输反序列化接收时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体 (类)的serialVersionUID
进行比较,如果相同就认为是一致的, 可以进行反序列化,否则就会出现序列化版本不一致的异常。
例如上述代码中,写入的A.ser
文件中版本序列号为1,现将其变为2,并仅运行input()
,则报错如下:Exception in thread "main" java.io.InvalidClassException: org.example.serial.A; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
如果在序列化时没有指定版本号,JVM会生成版本号。
// 对此类执行序列化
class A implements Serializable {
int x;
transient B b;
}
output();
----
// 减少属性后执行反序列化
class A implements Serializable {
int x;
}
input();
如果我们将没有指定版本号的类序列化到A.ser
中,再在增减属性后尝试反序列化,则报错如下:Exception in thread "main" java.io.InvalidClassException: org.example.serial.A; local class incompatible: stream classdesc serialVersionUID = -8586817179612647591, local class serialVersionUID = -4435318764304933042
。说明JVM在序列化时给定了序列号-8586817179612647591
,而当前JVM中的(即减少属性后的)A类的序列号为-4435318764304933042
,发生冲突。且生成序列号的方式不是随机的,猜测是根据字节码文件哈希得来的。
而只要版本号相同,增减属性,也能反序列化。增加的属性会赋值为0值,减少的属性会被忽略(即已没法访问)。
// 对此类执行序列化
class A implements Serializable {
private static final long serialVersionUID = 2L;
int x;
transient B b;
}
output();
System.out.println(a); // org.example.serial.A@1b6d3586
System.out.println(a.x); // 100
System.out.println(a.b); // org.example.serial.B@4554617c
// 保持版本号一致,减少属性后执行反序列化
class A implements Serializable {
private static final long serialVersionUID = 2L;
int x;
}
input();
System.out.println(a); // org.example.serial.A@58372a00
System.out.println(a.x); // 100
// 保持版本号一致,增加属性后执行反序列化
class A implements Serializable {
private static final long serialVersionUID = 2L;
int x;
transient B b;
String d;
}
input();
System.out.println(a); // org.example.serial.A@58372a00
System.out.println(a.x); // 100
System.out.println(a.b); // null
System.out.println(a.d); // null 因为序列化时没这个值,直接给null
总之序列化版本号如其意,就是用来控制类的不同版本,相同版本才能去兼容。