java序列化与反序列化 | 张扎瓦的博客

java序列化与反序列化

Java中对象的序列化与反序列化


定义

序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

以上是摘自维基百科序列化的定义

通俗的讲,序列化,就是把程序中的对象信息,通过某种方式,存入硬盘或者其他介质中。而反序列化就是将存储的内容重新转换为对象。

序列化

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class TestSerialize {
@Test
public void serialize() {
try (FileOutputStream fos = new FileOutputStream("a.clz");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {

Student tom = new Student("tom", 99.5, 1);
oos.writeObject(tom);
} catch (IOException e) {
e.printStackTrace();
}
}
}

class Student implements Serializable {
private String name;
private Double score;
private int sex;

public Student() {
}

public Student(String name, Double score, int sex) {
this.name = name;
this.score = score;
this.sex = sex;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getScore() {
return score;
}

public void setScore(Double score) {
this.score = score;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}

Java中的序列化很简单,直接使用java.io.ObjectOutputStream类的writeObject()方法即可。

执行完程序后,刷新当前项目文件夹,会有一个a.clz文件。

这里需要注意一点,要进行序列化,首先目标类必须实现Serializable接口。Serializable接口是一个空的接口,没有任何方法的定义,只是一个标记状态,表示可以进行序列化。

反序列化

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class TestSerialize {
@Test
public void unSerialize() {
try (FileInputStream fis = new FileInputStream("a.clz");
ObjectInputStream ois = new ObjectInputStream(fis)) {

Student tom = (Student) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

class Student implements Serializable {
private String name;
private Double score;
private int sex;

public Student() {
}

public Student(String name, Double score, int sex) {
this.name = name;
this.score = score;
this.sex = sex;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getScore() {
return score;
}

public void setScore(Double score) {
this.score = score;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}

反序列化,使用java.io.ObjectInputStream类的readObject()方法即可。

serialVersionUID作用

通常情况下,我们都会在实现序列化接口的同时,指定序列化id,就像这样

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
38
39
40
41
42
43
class Student implements Serializable {
/**
* 指定序列化id
*/
private static final long serialVersionUID = -2284240240714138081L;

private String name;
private Double score;
private int sex;

public Student() {
}

public Student(String name, Double score, int sex) {
this.name = name;
this.score = score;
this.sex = sex;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getScore() {
return score;
}

public void setScore(Double score) {
this.score = score;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}

这里的serialVersionUID究竟有什么用?这里的序列化id,是方便修改了类后,反序列化时使用的。

下面我们做一个测试。

首先将类中的serialVersionUID移除,然后进行序列化。

序列化1

修改目标类 Student,增加属性 school。

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
38
39
40
41
42
43
44
45
46
47
48
49
class Student implements Serializable {

private String name;
private Double score;
private int sex;
// 这是新增的
private String school;

public Student() {
}

public Student(String name, Double score, int sex) {
this.name = name;
this.score = score;
this.sex = sex;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getScore() {
return score;
}

public void setScore(Double score) {
this.score = score;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}

public String getSchool() {
return school;
}

public void setSchool(String school) {
this.school = school;
}
}

进行反序列化

反序列化1

当修改了类后,反序列化抛出异常

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
java.io.InvalidClassException: com.huayunworld.sas.Student; local class incompatible: stream classdesc serialVersionUID = -2284240240714138081, local class serialVersionUID = 7916950878587737932
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at com.huayunworld.sas.TestSerialize.unSerialize(TestSerialize.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

原因分析:当未指定serialVersionUID时,在序列化时,Java会自动创建一个随机的long类型的数字当作serialVersionUID,而修改了类后,代码重新编译,又会生成一个新的serialVersionUID,反序列化时因为serialVersionUID不同,所以导致反序列化失败。

当手动指定了serialVersionUID后,就不会出现反序列化失败了,大家可以自行测试验证。

transient关键字

当类的中的某些字段不想被序列化时,可以使用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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Student implements Serializable {

private static final long serialVersionUID = 7916950878587737932L;

private String name;
private Double score;
private int sex;

// 使用transient关键字修饰,表示此字段不参与序列化
private transient String school;

public Student() {
}

public Student(String name, Double score, int sex) {
this.name = name;
this.score = score;
this.sex = sex;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getScore() {
return score;
}

public void setScore(Double score) {
this.score = score;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}

public String getSchool() {
return school;
}

public void setSchool(String school) {
this.school = school;
}
}
如果我的文章对您有所帮助,不妨打赏一杯豆浆以资鼓励(○` 3′○)