java io系列06之 序列化总结(Serializable 和 Externalizable)
本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的2种方式Serializable和Externalizable的深入研究。
转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_06.html
1. 序列化是的作用和用途
序列化,就是为了保存对象的状态;而与之对应的反序列化,则可以把保存的对象状态再读出来。
简言之:序列化/反序列化,是Java提供一种专门用于的保存/恢复对象状态的机制。
一般在以下几种情况下,我们可能会用到序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候。
2. 演示程序1
下面,我们先通过一则简单示例来查看序列化的用法。
源码如下(SerialTest1.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12
13 public class SerialTest1 {
14 private static final String TMP_FILE = ".serialtest1.txt";
15
16 public static void main(String[] args) {
17 // 将“对象”通过序列化保存
18 testWrite();
19 // 将序列化的“对象”读出来
20 testRead();
21 }
22
23
24 /**
25 * 将Box对象通过序列化,保存到文件中
26 */
27 private static void testWrite() {
28 try {
29 // 获取文件TMP_FILE对应的对象输出流。
30 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
31 ObjectOutputStream out = new ObjectOutputStream(
32 new FileOutputStream(TMP_FILE));
33 // 创建Box对象,Box实现了Serializable序列化接口
34 Box box = new Box("desk", 80, 48);
35 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
36 out.writeObject(box);
37 // 打印“Box对象”
38 System.out.println("testWrite box: " + box);
39
40 out.close();
41 } catch (Exception ex) {
42 ex.printStackTrace();
43 }
44 }
45
46 /**
47 * 从文件中读取出“序列化的Box对象”
48 */
49 private static void testRead() {
50 try {
51 // 获取文件TMP_FILE对应的对象输入流。
52 ObjectInputStream in = new ObjectInputStream(
53 new FileInputStream(TMP_FILE));
54 // 从对象输入流中,读取先前保存的box对象。
55 Box box = (Box) in.readObject();
56 // 打印“Box对象”
57 System.out.println("testRead box: " + box);
58 in.close();
59 } catch (Exception e) {
60 e.printStackTrace();
61 }
62 }
63 }
64
65
66 /**
67 * Box类“支持序列化”。因为Box实现了Serializable接口。
68 *
69 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
70 */
71 class Box implements Serializable {
72 private int width;
73 private int height;
74 private String name;
75
76 public Box(String name, int width, int height) {
77 this.name = name;
78 this.width = width;
79 this.height = height;
80 }
81
82 @Override
83 public String toString() {
84 return "["+name+": ("+width+", "+height+") ]";
85 }
86 }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (80, 48) ]
源码说明:
(01) 程序的作用很简单,就是演示:先将Box对象,通过对象输出流保存到文件中;之后,再通过对象输入流,将文件中保存的Box对象读取出来。
(02) Box类说明。Box是我们自定义的演示类,它被用于序列化的读写。Box实现了Serialable接口,因此它支持序列化操作;即,Box支持通过 ObjectOutputStream去写入到输出流中,并且支持通过ObjectInputStream从输入流中读取出来。
(03) testWrite()函数说明。testWrite()的作用就是,新建一个Box对象,然后将该Box对象写入到文件中。
首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
a) 关于FileInputStream和FileOutputStream的内容,可以参考“java io系列07之 FileInputStream和FileOutputStream”。
b) 关于ObjectInputStream和ObjectOutputStream的的更多知识,可以参考“java io系列05之 ObjectInputStream 和 ObjectOutputStream”
然后,新建Box对象。
最后,通过out.writeObject(box) 将box写入到对象输出流中。实际上,相当于将box写入到文件TMP_FILE中。
(04) testRead()函数说明。testRead()的作用就是,从文件中读出Box对象。
首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
然后,通过in.readObject() 从对象输入流中读取出Box对象。实际上,相当于从文件TMP_FILE中读取Box对象。
通过上面的示例,我们知道:我们可以自定义类,让它支持序列化(即实现Serializable接口),从而能支持对象的保存/恢复。
若要支持序列化,除了“自定义实现Serializable接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。我们通过下面的示例去查看一下。
3. 演示程序2
源码如下(SerialTest2.java):
1 /**
2 * “基本类型” 和 “java自带的实现Serializable接口的类” 对序列化的支持
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.util.Map;
13 import java.util.HashMap;
14 import java.util.Iterator;
15
16 public class SerialTest2 {
17 private static final String TMP_FILE = ".serialabletest2.txt";
18
19 public static void main(String[] args) {
20 testWrite();
21 testRead();
22 }
23
24 /**
25 * ObjectOutputStream 测试函数
26 */
27 private static void testWrite() {
28 try {
29 ObjectOutputStream out = new ObjectOutputStream(
30 new FileOutputStream(TMP_FILE));
31 out.writeBoolean(true); // 写入Boolean值
32 out.writeByte((byte)65);// 写入Byte值
33 out.writeChar('a'); // 写入Char值
34 out.writeInt(20131015); // 写入Int值
35 out.writeFloat(3.14F); // 写入Float值
36 out.writeDouble(1.414D);// 写入Double值
37 // 写入HashMap对象
38 HashMap map = new HashMap();
39 map.put("one", "red");
40 map.put("two", "green");
41 map.put("three", "blue");
42 out.writeObject(map);
43
44 out.close();
45 } catch (Exception ex) {
46 ex.printStackTrace();
47 }
48 }
49
50 /**
51 * ObjectInputStream 测试函数
52 */
53 private static void testRead() {
54 try {
55 ObjectInputStream in = new ObjectInputStream(
56 new FileInputStream(TMP_FILE));
57 System.out.printf("boolean:%b\n" , in.readBoolean());
58 System.out.printf("byte:%d\n" , (in.readByte()&0xff));
59 System.out.printf("char:%c\n" , in.readChar());
60 System.out.printf("int:%d\n" , in.readInt());
61 System.out.printf("float:%f\n" , in.readFloat());
62 System.out.printf("double:%f\n" , in.readDouble());
63 // 读取HashMap对象
64 HashMap map = (HashMap) in.readObject();
65 Iterator iter = map.entrySet().iterator();
66 while (iter.hasNext()) {
67 Map.Entry entry = (Map.Entry)iter.next();
68 System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());
69 }
70
71 in.close();
72 } catch (Exception e) {
73 e.printStackTrace();
74 }
75 }
76 }
运行结果:
boolean:true
byte:65
char:a
int:20131015
float:3.140000
double:1.414000
two -- green
one -- red
three -- blue
源码说明:
(01) 程序的作用很简单,就是演示:先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中;之后,再通过对象输入流,将这些保存的数据读取出来。
(02) testWrite()函数说明。testWrite()的作用就是,先将“基本类型数据”和“HashMap对象”,通过对象输出流保存到文件中。
首先,新建文件TMP_FILE的文件输出流对象(即FileOutputStream对象),再创建该文件输出流的对象输出流(即ObjectOutputStream对象)。
然后,通过 writeBoolean(), writeByte(), ... , writeDouble()
等一系列函数将“Boolean, byte, char, ... ,
double等基本数据类型”写入到对象输出流中。实际上,相当于将这些内容写入到文件TMP_FILE中。
最后,新建HashMap对象map,并通过out.writeObject(map) 将map写入到对象输出流中。实际上,相当于map写入到文件TMP_FILE中。
关于HashMap的更多知识,可以参考“Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例”。
(03) testRead()函数说明。testRead()的作用就是,从文件中读出testWrite()写入的对象。
首先,新建文件TMP_FILE的文件输入流对象(即FileInputStream对象),再创建该文件输入流的对象输入流(即ObjectInputStream对象)。
然后,通过in.readObject() 从对象输入流中读取出testWrite()对象。实际上,相当于从文件TMP_FILE中读取出这些对象。
在前面,我们提到过:若要支持序列化,除了“自定义实现Serializable
接口的类”之外;java的“基本类型”和“java自带的实现了Serializable接口的类”,都支持序列化。为了验证这句话,我们看看
HashMap是否实现了Serializable接口。
HashMap是java.util包中定义的类,它的接口声明如下:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
至此,我们对序列化的认识已经比较深入了:即知道了“序列化的作用和用法”,也知道了“基本类型”、“java自带的支持Serializable接口的类”和“自定义实现Serializable接口的类”都能支持序列化。
应
付序列化的简单使用应该足够了。但是,我们的目的是对序列化有更深层次的了解!更何况,写此文的作者(也就是区区在下),应该比各位看官要累(既要写代
码,又要总结,还得注意排版和用词,讲的通俗易懂,让各位看得轻松自在);我这个菜鸟都能做到这些,何况对知识极其渴望的您呢?所以,请深吸一口气,然后
继续……
我们在介绍序列化定义时,说过“序列化/反序列化,是专门用于的保存/恢复对象状态的机制”。
从中,我们知道:序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法!
但是,序列化是不是对类的所有的成员变量的状态都能保存呢?
答案当然是否定的!
(01) 序列化对static和transient变量,是不会自动进行状态保存的。
transient的作用就是,用transient声明的变量,不会被自动序列化。
(02) 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!
这主要是基于资源分配方面的原因。如果Socket,Thread类可以被序列化,但是被反序列化之后也无法对他们进行重新的资源分配;再者,也是没有必要这样实现。
下面,我们还是通过示例来查看“序列化对static和transient的处理”。
4. 演示程序3
我们对前面的SerialTest1.java进行简单修改,得到源文件(SerialTest3.java)如下:
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12
13 public class SerialTest3 {
14 private static final String TMP_FILE = ".serialtest3.txt";
15
16 public static void main(String[] args) {
17 // 将“对象”通过序列化保存
18 testWrite();
19 // 将序列化的“对象”读出来
20 testRead();
21 }
22
23
24 /**
25 * 将Box对象通过序列化,保存到文件中
26 */
27 private static void testWrite() {
28 try {
29 // 获取文件TMP_FILE对应的对象输出流。
30 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
31 ObjectOutputStream out = new ObjectOutputStream(
32 new FileOutputStream(TMP_FILE));
33 // 创建Box对象,Box实现了Serializable序列化接口
34 Box box = new Box("desk", 80, 48);
35 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
36 out.writeObject(box);
37 // 打印“Box对象”
38 System.out.println("testWrite box: " + box);
39
40 out.close();
41 } catch (Exception ex) {
42 ex.printStackTrace();
43 }
44 }
45
46 /**
47 * 从文件中读取出“序列化的Box对象”
48 */
49 private static void testRead() {
50 try {
51 // 获取文件TMP_FILE对应的对象输入流。
52 ObjectInputStream in = new ObjectInputStream(
53 new FileInputStream(TMP_FILE));
54 // 从对象输入流中,读取先前保存的box对象。
55 Box box = (Box) in.readObject();
56 // 打印“Box对象”
57 System.out.println("testRead box: " + box);
58 in.close();
59 } catch (Exception e) {
60 e.printStackTrace();
61 }
62 }
63 }
64
65
66 /**
67 * Box类“支持序列化”。因为Box实现了Serializable接口。
68 *
69 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
70 */
71 class Box implements Serializable {
72 private static int width;
73 private transient int height;
74 private String name;
75
76 public Box(String name, int width, int height) {
77 this.name = name;
78 this.width = width;
79 this.height = height;
80 }
81
82 @Override
83 public String toString() {
84 return "["+name+": ("+width+", "+height+") ]";
85 }
86 }
SerialTest3.java 相比于 SerialTest1.java。仅仅对Box类中的 width 和 height 变量的定义进行了修改。
SerialTest1.java 中width和height定义
private int width;
private int height;
SerialTest3.java 中width和height定义
private static int width;
private transient int height;
在看后面的结果之前,我们建议大家对程序进行分析,先自己得出一个结论。
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (80, 0) ]
结果分析:
我们前面说过,“序列化不对static和transient变量进行状态保 存”。因此,testWrite()中保存Box对象时,不会保存width和height的值。这点是毋庸置疑的!但是,为什么testRead()中 读取出来的Box对象的width=80,而height=0呢?
先说,为什么height=0。因为Box对象中height是int类型,而int类型的默认值是0。
再
说,为什么width=80。这是因为height是static类型,而static类型就意味着所有的Box对象都共用一个height值;而在
testWrite()中,我们已经将height初始化为80了。因此,我们通过序列化读取出来的Box对象的height值,也被就是80。
理解上面的内容之后,我们应该可以推断出下面的代码的运行结果。
源码如下(SerialTest4.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12
13 public class SerialTest4 {
14 private static final String TMP_FILE = ".serialtest4.txt";
15
16 public static void main(String[] args) {
17 // 将“对象”通过序列化保存
18 testWrite();
19 // 将序列化的“对象”读出来
20 testRead();
21 }
22
23
24 /**
25 * 将Box对象通过序列化,保存到文件中
26 */
27 private static void testWrite() {
28 try {
29 // 获取文件TMP_FILE对应的对象输出流。
30 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
31 ObjectOutputStream out = new ObjectOutputStream(
32 new FileOutputStream(TMP_FILE));
33 // 创建Box对象,Box实现了Serializable序列化接口
34 Box box = new Box("desk", 80, 48);
35 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
36 out.writeObject(box);
37 // 打印“Box对象”
38 System.out.println("testWrite box: " + box);
39 // 修改box的值
40 box = new Box("room", 100, 50);
41
42 out.close();
43 } catch (Exception ex) {
44 ex.printStackTrace();
45 }
46 }
47
48 /**
49 * 从文件中读取出“序列化的Box对象”
50 */
51 private static void testRead() {
52 try {
53 // 获取文件TMP_FILE对应的对象输入流。
54 ObjectInputStream in = new ObjectInputStream(
55 new FileInputStream(TMP_FILE));
56 // 从对象输入流中,读取先前保存的box对象。
57 Box box = (Box) in.readObject();
58 // 打印“Box对象”
59 System.out.println("testRead box: " + box);
60 in.close();
61 } catch (Exception e) {
62 e.printStackTrace();
63 }
64 }
65 }
66
67
68 /**
69 * Box类“支持序列化”。因为Box实现了Serializable接口。
70 *
71 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
72 */
73 class Box implements Serializable {
74 private static int width;
75 private transient int height;
76 private String name;
77
78 public Box(String name, int width, int height) {
79 this.name = name;
80 this.width = width;
81 this.height = height;
82 }
83
84 @Override
85 public String toString() {
86 return "["+name+": ("+width+", "+height+") ]";
87 }
88 }
SerialTest4.java 相比于 SerialTest3.java,在testWrite()中添加了一行代码box = new Box("room", 100, 50);
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (100, 0) ]
现在,我们更加确认“序列化不对static和transient变量进行状态保存”。但是,若我们想要保存static或transient变量,能不能办到呢?
当然可以!我们在类中重写两个方法writeObject()和readObject()即可。下面程序演示了如何手动保存static和transient变量。
5. 演示程序4
我们对前面的SerialTest4.java进行简单修改,以达到:序列化存储static和transient变量的目的。
源码如下(SerialTest5.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.io.IOException;
13 import java.lang.ClassNotFoundException;
14
15 public class SerialTest5 {
16 private static final String TMP_FILE = ".serialtest5.txt";
17
18 public static void main(String[] args) {
19 // 将“对象”通过序列化保存
20 testWrite();
21 // 将序列化的“对象”读出来
22 testRead();
23 }
24
25
26 /**
27 * 将Box对象通过序列化,保存到文件中
28 */
29 private static void testWrite() {
30 try {
31 // 获取文件TMP_FILE对应的对象输出流。
32 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
33 ObjectOutputStream out = new ObjectOutputStream(
34 new FileOutputStream(TMP_FILE));
35 // 创建Box对象,Box实现了Serializable序列化接口
36 Box box = new Box("desk", 80, 48);
37 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
38 out.writeObject(box);
39 // 打印“Box对象”
40 System.out.println("testWrite box: " + box);
41 // 修改box的值
42 box = new Box("room", 100, 50);
43
44 out.close();
45 } catch (Exception ex) {
46 ex.printStackTrace();
47 }
48 }
49
50 /**
51 * 从文件中读取出“序列化的Box对象”
52 */
53 private static void testRead() {
54 try {
55 // 获取文件TMP_FILE对应的对象输入流。
56 ObjectInputStream in = new ObjectInputStream(
57 new FileInputStream(TMP_FILE));
58 // 从对象输入流中,读取先前保存的box对象。
59 Box box = (Box) in.readObject();
60 // 打印“Box对象”
61 System.out.println("testRead box: " + box);
62 in.close();
63 } catch (Exception e) {
64 e.printStackTrace();
65 }
66 }
67 }
68
69
70 /**
71 * Box类“支持序列化”。因为Box实现了Serializable接口。
72 *
73 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
74 */
75 class Box implements Serializable {
76 private static int width;
77 private transient int height;
78 private String name;
79
80 public Box(String name, int width, int height) {
81 this.name = name;
82 this.width = width;
83 this.height = height;
84 }
85
86 private void writeObject(ObjectOutputStream out) throws IOException{
87 out.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
88 out.writeInt(height);
89 out.writeInt(width);
90 //System.out.println("Box--writeObject width="+width+", height="+height);
91 }
92
93 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
94 in.defaultReadObject();//defaultReadObject()补充自动序列化
95 height = in.readInt();
96 width = in.readInt();
97 //System.out.println("Box---readObject width="+width+", height="+height);
98 }
99
100 @Override
101 public String toString() {
102 return "["+name+": ("+width+", "+height+") ]";
103 }
104 }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (80, 48) ]
程序说明:
“序列化不会自动保存static和transient变量”,因此我们若要保存它们,则需要通过writeObject()和readObject()去手动读写。
(01) 通过writeObject()方法,写入要保存的变量。writeObject的原始定义是在ObjectOutputStream.java中,我们按照如下示例覆盖即可:
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();// 使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
out.writeInt(ival); // 若要保存“int类型的值”,则使用writeInt()
out.writeObject(obj); // 若要保存“Object对象”,则使用writeObject()
}
(02) 通过readObject()方法,读取之前保存的变量。readObject的原始定义是在ObjectInputStream.java中,我们按照如下示例覆盖即可:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject(); // 使定制的readObject()方法可以利用自动序列化中内置的逻辑。
int ival = in.readInt(); // 若要读取“int类型的值”,则使用readInt()
Object obj = in.readObject(); // 若要读取“Object对象”,则使用readObject()
}
至此,我们就介绍完了“序列化对static和transient变量的处理”。
接下来,我们来研究“对于Socket, Thread类,不支持序列化”。还是通过示例来查看。
6. 演示程序5
我们修改SerialTest5.java的源码,在Box类中添加一个Thread成员。
源码如下(SerialTest6.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.lang.Thread;
13 import java.io.IOException;
14 import java.lang.ClassNotFoundException;
15
16 public class SerialTest6 {
17 private static final String TMP_FILE = ".serialtest6.txt";
18
19 public static void main(String[] args) {
20 // 将“对象”通过序列化保存
21 testWrite();
22 // 将序列化的“对象”读出来
23 testRead();
24 }
25
26
27 /**
28 * 将Box对象通过序列化,保存到文件中
29 */
30 private static void testWrite() {
31 try {
32 // 获取文件TMP_FILE对应的对象输出流。
33 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
34 ObjectOutputStream out = new ObjectOutputStream(
35 new FileOutputStream(TMP_FILE));
36 // 创建Box对象,Box实现了Serializable序列化接口
37 Box box = new Box("desk", 80, 48);
38 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
39 out.writeObject(box);
40 // 打印“Box对象”
41 System.out.println("testWrite box: " + box);
42 // 修改box的值
43 box = new Box("room", 100, 50);
44
45 out.close();
46 } catch (Exception ex) {
47 ex.printStackTrace();
48 }
49 }
50
51 /**
52 * 从文件中读取出“序列化的Box对象”
53 */
54 private static void testRead() {
55 try {
56 // 获取文件TMP_FILE对应的对象输入流。
57 ObjectInputStream in = new ObjectInputStream(
58 new FileInputStream(TMP_FILE));
59 // 从对象输入流中,读取先前保存的box对象。
60 Box box = (Box) in.readObject();
61 // 打印“Box对象”
62 System.out.println("testRead box: " + box);
63 in.close();
64 } catch (Exception e) {
65 e.printStackTrace();
66 }
67 }
68 }
69
70
71 /**
72 * Box类“支持序列化”。因为Box实现了Serializable接口。
73 *
74 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
75 */
76 class Box implements Serializable {
77 private static int width;
78 private transient int height;
79 private String name;
80 private Thread thread = new Thread() {
81 @Override
82 public void run() {
83 System.out.println("Serializable thread");
84 }
85 };
86
87 public Box(String name, int width, int height) {
88 this.name = name;
89 this.width = width;
90 this.height = height;
91 }
92
93 private void writeObject(ObjectOutputStream out) throws IOException{
94 out.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
95 out.writeInt(height);
96 out.writeInt(width);
97 //System.out.println("Box--writeObject width="+width+", height="+height);
98 }
99
100 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
101 in.defaultReadObject();//defaultReadObject()补充自动序列化
102 height = in.readInt();
103 width = in.readInt();
104 //System.out.println("Box---readObject width="+width+", height="+height);
105 }
106
107 @Override
108 public String toString() {
109 return "["+name+": ("+width+", "+height+") ]";
110 }
111 }
结果是,编译出错!
事实证明,不能对Thread进行序列化。若希望程序能编译通过,我们对Thread变量添加static或transient修饰即可!如下,是对Thread添加transient修饰的源码(SerialTest7.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.lang.Thread;
13 import java.io.IOException;
14 import java.lang.ClassNotFoundException;
15
16 public class SerialTest7 {
17 private static final String TMP_FILE = ".serialtest7.txt";
18
19 public static void main(String[] args) {
20 // 将“对象”通过序列化保存
21 testWrite();
22 // 将序列化的“对象”读出来
23 testRead();
24 }
25
26
27 /**
28 * 将Box对象通过序列化,保存到文件中
29 */
30 private static void testWrite() {
31 try {
32 // 获取文件TMP_FILE对应的对象输出流。
33 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
34 ObjectOutputStream out = new ObjectOutputStream(
35 new FileOutputStream(TMP_FILE));
36 // 创建Box对象,Box实现了Serializable序列化接口
37 Box box = new Box("desk", 80, 48);
38 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
39 out.writeObject(box);
40 // 打印“Box对象”
41 System.out.println("testWrite box: " + box);
42 // 修改box的值
43 box = new Box("room", 100, 50);
44
45 out.close();
46 } catch (Exception ex) {
47 ex.printStackTrace();
48 }
49 }
50
51 /**
52 * 从文件中读取出“序列化的Box对象”
53 */
54 private static void testRead() {
55 try {
56 // 获取文件TMP_FILE对应的对象输入流。
57 ObjectInputStream in = new ObjectInputStream(
58 new FileInputStream(TMP_FILE));
59 // 从对象输入流中,读取先前保存的box对象。
60 Box box = (Box) in.readObject();
61 // 打印“Box对象”
62 System.out.println("testRead box: " + box);
63 in.close();
64 } catch (Exception e) {
65 e.printStackTrace();
66 }
67 }
68 }
69
70
71 /**
72 * Box类“支持序列化”。因为Box实现了Serializable接口。
73 *
74 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
75 */
76 class Box implements Serializable {
77 private static int width;
78 private transient int height;
79 private String name;
80 private transient Thread thread = new Thread() {
81 @Override
82 public void run() {
83 System.out.println("Serializable thread");
84 }
85 };
86
87 public Box(String name, int width, int height) {
88 this.name = name;
89 this.width = width;
90 this.height = height;
91 }
92
93 private void writeObject(ObjectOutputStream out) throws IOException{
94 out.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
95 out.writeInt(height);
96 out.writeInt(width);
97 //System.out.println("Box--writeObject width="+width+", height="+height);
98 }
99
100 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
101 in.defaultReadObject();//defaultReadObject()补充自动序列化
102 height = in.readInt();
103 width = in.readInt();
104 //System.out.println("Box---readObject width="+width+", height="+height);
105 }
106
107 @Override
108 public String toString() {
109 return "["+name+": ("+width+", "+height+") ]";
110 }
111 }
至此,关于“Serializable接口”来实现序列化的内容,都说完了。为什 么这么说?因为,实现序列化,除了Serializable之外,还有其它的方式,就是通过实现Externalizable来实现序列化。整理下心情, 下面继续对Externalizable进行了解。
7. Externalizable和完全定制序列化过程
如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。
Externalizable接口定义包括两个方法 writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。 writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信 息,则要格外小心。
下面,我们修改之前的SerialTest1.java测试程序;将其中的Box由“实现Serializable接口” 改为 “实现Externalizable接口”。
修改后的源码如下( ExternalizableTest1.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.ObjectOutput;
12 import java.io.ObjectInput;
13 import java.io.Serializable;
14 import java.io.Externalizable;
15 import java.io.IOException;
16 import java.lang.ClassNotFoundException;
17
18 public class ExternalizableTest1 {
19 private static final String TMP_FILE = ".externalizabletest1.txt";
20
21 public static void main(String[] args) {
22 // 将“对象”通过序列化保存
23 testWrite();
24 // 将序列化的“对象”读出来
25 testRead();
26 }
27
28
29 /**
30 * 将Box对象通过序列化,保存到文件中
31 */
32 private static void testWrite() {
33 try {
34 // 获取文件TMP_FILE对应的对象输出流。
35 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
36 ObjectOutputStream out = new ObjectOutputStream(
37 new FileOutputStream(TMP_FILE));
38 // 创建Box对象
39 Box box = new Box("desk", 80, 48);
40 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
41 out.writeObject(box);
42 // 打印“Box对象”
43 System.out.println("testWrite box: " + box);
44
45 out.close();
46 } catch (Exception ex) {
47 ex.printStackTrace();
48 }
49 }
50
51 /**
52 * 从文件中读取出“序列化的Box对象”
53 */
54 private static void testRead() {
55 try {
56 // 获取文件TMP_FILE对应的对象输入流。
57 ObjectInputStream in = new ObjectInputStream(
58 new FileInputStream(TMP_FILE));
59 // 从对象输入流中,读取先前保存的box对象。
60 Box box = (Box) in.readObject();
61 // 打印“Box对象”
62 System.out.println("testRead box: " + box);
63 in.close();
64 } catch (Exception e) {
65 e.printStackTrace();
66 }
67 }
68 }
69
70
71 /**
72 * Box类实现Externalizable接口
73 */
74 class Box implements Externalizable {
75 private int width;
76 private int height;
77 private String name;
78
79 public Box() {
80 }
81
82 public Box(String name, int width, int height) {
83 this.name = name;
84 this.width = width;
85 this.height = height;
86 }
87
88 @Override
89 public void writeExternal(ObjectOutput out) throws IOException {
90 }
91
92 @Override
93 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
94 }
95
96 @Override
97 public String toString() {
98 return "["+name+": ("+width+", "+height+") ]";
99 }
100 }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [null: (0, 0) ]
说明:
(01) 实现Externalizable接口的类,不会像实现Serializable接口那样,会自动将数据保存。
(02) 实现Externalizable接口的类,必须实现writeExternal()和readExternal()接口!
否则,程序无法正常编译!
(03) 实现Externalizable接口的类,必须定义不带参数的构造函数!
否则,程序无法正常编译!
(04) writeExternal() 和 readExternal() 的方法都是public的,不是非常安全!
接着,我们修改上面的ExternalizableTest1.java测试程序;实现Box类中的writeExternal()和readExternal()接口!
修改后的源码如下( ExternalizableTest2.java):
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.ObjectOutput;
12 import java.io.ObjectInput;
13 import java.io.Serializable;
14 import java.io.Externalizable;
15 import java.io.IOException;
16 import java.lang.ClassNotFoundException;
17
18 public class ExternalizableTest2 {
19 private static final String TMP_FILE = ".externalizabletest2.txt";
20
21 public static void main(String[] args) {
22 // 将“对象”通过序列化保存
23 testWrite();
24 // 将序列化的“对象”读出来
25 testRead();
26 }
27
28
29 /**
30 * 将Box对象通过序列化,保存到文件中
31 */
32 private static void testWrite() {
33 try {
34 // 获取文件TMP_FILE对应的对象输出流。
35 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
36 ObjectOutputStream out = new ObjectOutputStream(
37 new FileOutputStream(TMP_FILE));
38 // 创建Box对象
39 Box box = new Box("desk", 80, 48);
40 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
41 out.writeObject(box);
42 // 打印“Box对象”
43 System.out.println("testWrite box: " + box);
44
45 out.close();
46 } catch (Exception ex) {
47 ex.printStackTrace();
48 }
49 }
50
51 /**
52 * 从文件中读取出“序列化的Box对象”
53 */
54 private static void testRead() {
55 try {
56 // 获取文件TMP_FILE对应的对象输入流。
57 ObjectInputStream in = new ObjectInputStream(
58 new FileInputStream(TMP_FILE));
59 // 从对象输入流中,读取先前保存的box对象。
60 Box box = (Box) in.readObject();
61 // 打印“Box对象”
62 System.out.println("testRead box: " + box);
63 in.close();
64 } catch (Exception e) {
65 e.printStackTrace();
66 }
67 }
68 }
69
70
71 /**
72 * Box类实现Externalizable接口
73 */
74 class Box implements Externalizable {
75 private int width;
76 private int height;
77 private String name;
78
79 public Box() {
80 }
81
82 public Box(String name, int width, int height) {
83 this.name = name;
84 this.width = width;
85 this.height = height;
86 }
87
88 @Override
89 public void writeExternal(ObjectOutput out) throws IOException {
90 out.writeObject(name);
91 out.writeInt(width);
92 out.writeInt(height);
93 }
94
95 @Override
96 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
97 name = (String) in.readObject();
98 width = in.readInt();
99 height = in.readInt();
100 }
101
102 @Override
103 public String toString() {
104 return "["+name+": ("+width+", "+height+") ]";
105 }
106 }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (80, 48) ]
至此,序列化的内容就全部讲完了。更多相关的内容,请参考: