1、什么是序列化和反序列化

  • Serialization(序列化)是一种将对象以一连串的字节描述的过程。
  • 反序列化deserialization是一种将这些字节重建成一个对象的过程。

2、什么情况下需要序列化

  • 当你想把的内存中的对象保存到一个文件中或者数据库中时候(数据持久化);
  • 利用序列化实现远程通信,即在网络上传送对象的字节序列;

3、如何实现序列化

  • 将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以序列化.

4、序列化和反序列化例子

  • 如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。
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
53
54
package com.serialize;  

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Serialize implements Serializable{

private static final long serialVersionUID = -5211389707739541364L;
public int num = 1390;

public void serialized(){
try {
FileOutputStream fos = new FileOutputStream("serialize.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Serialize serialize = new Serialize();
oos.writeObject(serialize);
oos.flush();
oos.close();//只是为了做个例子,真实编程要放到finally下
fos.close();
System.out.println("序列化结束");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public void deserialized(){
Serialize serialize = null;
try {
FileInputStream fis = new FileInputStream("serialize.obj");
ObjectInputStream ois = new ObjectInputStream(fis);

serialize = (Serialize) ois.readObject();
ois.close();
fis.close();
System.out.println("反序列化结束");
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
System.out.println(serialize.num);
}

public static void main(String[] args) {
Serialize serialize = new Serialize();
serialize.serialized();
serialize.deserialized();
}
}

运行结果:

1
2
3
序列化结束  
反序列化结束
1390

5、序列化的数据含有那些信息

这里举个例子将上面例子中的serialize.obj的信息读取出来:

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
package com.serialize;  

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ReadSerialize {

public static void main(String[] args) {
try {
File file = new File("serialize.obj");
InputStream in = new FileInputStream(file);
byte buff[] = new byte[1024];
int len = 0;
while((len = in.read(buff)) !=-1) {
for(int i=0;i<len;i++) {
System.out.printf("%02X ",buff[i]);
}
System.out.println();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行结果

1
2
3
4
5
AC ED 00 05 73     
72 00 17 63 6F 6D 2E 73 65 72 69 61 6C 69 7A 65 2E 53 65 72 69 61 6C 69 7A 65 B7 AD 6C AC 04 0E D0 8C 02 00 01
49 00 03 6E 75 6D
78 70
00 00 05 6E
第一部分是序列化文件头
1
2
3
AC ED:STREAM_MAGIC声明使用了序列化协议  
00 05:STREAM_VERSION序列化协议版本
73:TC_OBJECT声明这是一个新的对象
第二部分是序列化类的描述
1
2
3
4
5
6
72:TC_CLASSDESC声明这里开始一个新class  
00 17:class名字的长度是23字节
63 6F 6D 2E 73 65 72 69 61 6C 69 7A 65 2E 53 65 72 69 61 6C 69 7A 65:完整类名
B7 AD 6C AC 04 0E D0 8C: SerialVersionUID
02:标记号,改值声明改对象支持序列化
00 01:该类所包含的域的个数为1
第三部分是对象中各个属性项的描述
1
2
3
49:域类型,代表I,表示Int类型(又如:44,查ASCII码表为D,代表Double类型)  
00 03:域名字的长度,为3
6E 75 6D: num属性的名称
第四部分输出该对象父类信息描述,这里没有父类,如果有,则数据格式与第二部分一样
1
2
78:TC_ENDBLOCKDATA,对象块接收标志  
70:TC_NULL,说明没有其他超类的标志
第五部分输出对象的属性的实际值,如果属性项是一个对象,那么这里还将序列化这个对象,规则和第2部分一样。
1
00 00 05 6E:1390的值

6、序列化前和序列化后的对象的关系

序列化时深复制,反序列化还原后的对象地址与原来的不同。

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
package com.serialize;  

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Serialize implements Serializable{

private static final long serialVersionUID = -5211389707739541364L;
public int num = 1390;

public void checkIsSame() {
Serialize serialize1 = new Serialize();
try {
FileOutputStream fos = new FileOutputStream("serialize.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(serialize1);
oos.flush();
oos.close();//只是为了做个例子,真实编程要放到finally下
fos.close();
System.out.println("序列化结束");
} catch (IOException e) {
e.printStackTrace();
}
Serialize serialize2 = null;
try {
FileInputStream fis = new FileInputStream("serialize.obj");
ObjectInputStream ois = new ObjectInputStream(fis);

serialize2 = (Serialize) ois.readObject();
ois.close();
fis.close();
System.out.println("反序列化结束");
} catch (ClassNotFoundException | IOException e){
e.printStackTrace();
}
System.out.println("序列化和反序列化的对象相同否?"+(serialize1==serialize2));
}

public static void main(String[] args) {
Serialize serialize = new Serialize();
serialize.checkIsSame();
}
}

运行结果:

1
2
3
序列化结束  
反序列化结束
序列化和反序列化的对象相同否?false

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)”——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

7、破坏单例模式

序列化和反序列化可能会破坏单例。上面的例子就是个很好的证明,为了更形象,在举一个单例模式的序列化的例子

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
package com.serialize;  

import java.io.Serializable;

public class SerSingleton implements Serializable {
private static final long serialVersionUID = 1L;

String name;

private SerSingleton(){
System.out.println("Singleton is create");
name="SerSingleton";
}

private static SerSingleton instance = new SerSingleton();

public static SerSingleton getInstance() {
return instance;
}

public static void createString(){
System.out.println("createString in Singleton");
}
}

@Test
public void test() throws IOException, ClassNotFoundException {
SerSingleton s1= null;
SerSingleton s = SerSingleton.getInstance();

FileOutputStream fos = new FileOutputStream("SerSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s);
oos.flush();
oos.close();

FileInputStream fis = new FileInputStream("SerSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerSingleton)ois.readObject();
System.out.println(s==s1);
}

运行结果

1
2
Singleton is create  
false

说明测试代码中的s和s1指向了不同的实例,在反序列化后,生成多个对象实例。稍微修改一下SerSingleton类,如下所示:

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
package com.serialize;  

import java.io.Serializable;

public class SerSingleton implements Serializable {
private static final long serialVersionUID = 1L;

String name;

private SerSingleton() {
System.out.println("Singleton is create");
name="SerSingleton";
}

private static SerSingleton instance = new SerSingleton();

public static SerSingleton getInstance() {
return instance;
}

public static void createString(){
System.out.println("createString in Singleton");
}

private Object readResolve(){
return instance;
}
}

运行测试代码,结果如下

1
2
Singleton is create  
true

至于为什么,ObjectIputStream.class的源码中有这样一段话

一般来说,对单例进行序列化和反序列化的场景并不多见,但如果存在,就要多加注意。

8、序列化ID

序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。

9、静态变量能否序列化

序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化。即 序列化的是对象的状态不是类的状态。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域。transient后的变量也不能序列化。

transient使用小结

  • 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  • transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
  • 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

总结

  • 当父类继承Serializable接口时,所有子类都可以被序列化。
  • 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,数据不会丢失),但是在子类中的属性仍能正确序列化
  • 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错。
  • 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  • 在反序列化时,如果serialVersionUID被序列化,则反序列化时会失败
  • 当一个对象的实例变量引用其他对象,序列化改对象时,也把引用对象进行序列化
  • static,transient后的变量不能被序列化