Java序列化与反序列化
序列化
概念:
将Java中的对象转换为字节序列
使用场景:
如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储);
在网络中传输对象
实现方式:
实现
java.io.Serializable
接口注意点:
基本类型、数组、枚举等也会进行序列化
transient修饰的属性和静态属性不参与序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38and the values of the non-transient
and non-static fields of the class and all of its supertypes are
written.
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
private void writeArray(xxx) {
if (ccl.isPrimitive()) {
if (ccl == Integer.TYPE) {
int[] ia = (int[]) array;
bout.writeInt(ia.length);
bout.writeInts(ia, 0, ia.length);
} else if (ccl == Byte.TYPE) {
byte[] ba = (byte[]) array;
bout.writeInt(ba.length);
bout.write(ba, 0, ba.length, true);
} else if (ccl == Long.TYPE) {
......
}
......
}
}代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38class Student implements Serializable {
private String name;
private int number;
// 被transient标记的属性不参与序列化,创建对象时赋值18,序列化后重新读取该属性的值为0
private transient int age;
// 静态属性不参与序列化,序列化后该属性值为null
private static String gender;
public void showStudent() {
System.out.println("name: " + name);
System.out.println("number: " + number);
System.out.println("age: " + age);
}
.......省略getter、setter方法
}
public void serializable() {
// 创建对象
Student student = new Student();
student.setName("小花");
student.setNumber(36);
student.setAge(18);
student.setGender("female");
String greeting = "HelloWorld";
// 创建FileOutputStream
// 创建ObjectOutputStream
try (FileOutputStream fos = new FileOutputStream("D:\\MyWork\\student.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
// 将对象写入到文件
oos.writeObject(student);
oos.writeObject(greeting);
} catch (IOException e) {
e.printStackTrace();
}
}
反序列化
概念:
将字节序列转换为Java对象
使用场景
如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储)
在网络中传输对象,接收字节序列后重新读取对象信息
注意点:
必须确保该读取程序的CLASSPATH中包含有序列化类的class文件
代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void deserializable() {
Student student = null;
String greeting = "";
// 创建FileInputStream
// 创建ObjectInputStream
try (FileInputStream fis = new FileInputStream("D:\\MyWork\\student.ser");
ObjectInputStream ois = new ObjectInputStream(fis);) {
// 从文件读取对象
student = (Student) ois.readObject();
greeting = (String) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
// 使用对象
if (null != student) {
student.showStudent();
}
System.out.println(greeting);
}
// 输出结果
name: 小花
number: 36
age: 0
gender: null
HelloWorld
serialVersionUID
概念:
序列化版本号,用来标识当前实现序列化接口的类,如果不显式指定,编译器会给类自动生成一个serialVersionUID
作用:
先看一个场景,在上述(1)中,Student类定义了name、number、age、gender四个属性,然后我们创建一个student对象并将其序列化保存到student.ser文件中,然后我们在Student类中增加一个属性,如className,再通过反序列化读取student.ser文件中的字节信息,转换成student对象,此时程序会报错
1
java.io.InvalidClassException: com.easyfun.base.Student; local class incompatible: stream classdesc serialVersionUID = -6313947761321746093, local class serialVersionUID = 4891437043098475710
根据异常信息找到相应的代码:java.io.ObjectStreamClass#initNonProxy,当原class的序列化版本号和当前class的序列化版本号不一致会导致反序列化失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14void initNonProxy(xxx){
long suid = Long.valueOf(model.getSerialVersionUID());
......
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());
}
......
}现在我们在Student类中添加上serialVersionUID(可以通过Idea自动生成,方法可自行搜索),然后调用序列化方法后,在Student类增加一个属性className,然后再调用反序列化方法,发现此次序列化不会再报错了
1
2
3
4
5
6
7
8
9
10private static final long serialVersionUID = -6313947761321746093L;
// 输出结果
name: 小花
number: 36
age: 0
gender: null
className: null
HelloWorld因此serialVersionUID的作用就比较好理解了,我们可以显式去指定某个类的序列化版本号,这样我们在反序列化时,即使该类的结构发生了变化,也能保证反序列化能够正常进行