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(); 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(); } }
|
运行结果:
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部分一样。
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(); 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后的变量不能被序列化