java io系列06之 序列化总结(Serializable 和 Externalizable)

本章,我们对序列化进行深入的学习和探讨。学习内容,包括序列化的作用、用途、用法,以及对实现序列化的2种方式SerializableExternalizable的深入研究。

转载请注明出处: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) ]

至此,序列化的内容就全部讲完了。更多相关的内容,请参考:

时间: 2024-10-22 07:40:07

java io系列06之 序列化总结(Serializable 和 Externalizable)的相关文章

java io系列20之 PipedReader和PipedWriter

本章,我们学习PipedReader和PipedWriter.它们和"PipedInputStream和PipedOutputStream"一样,都可以用于管道通信. PipedWriter 是字符管道输出流,它继承于Writer.PipedReader 是字符管道输入流,它继承于Writer.PipedWriter和PipedReader的作用是可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedWriter和PipedReader配套使用. 转载请注明出处:http:/

java io系列16之 PrintStream(打印输出流)详解

本章介绍PrintStream以及 它与DataOutputStream的区别.我们先对PrintStream有个大致认识,然后再深入学习它的源码,最后通过示例加深对它的了解. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_16.html PrintStream 介绍 PrintStream 是打印输出流,它继承于FilterOutputStream.PrintStream 是用来装饰其它输出流.它能为其他输出流添加了功能,使它们能够方便地打印

java io系列26之 RandomAccessFile

本文主要介绍 RandomAccessFile. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_26.html 更多内容请参考:java io系列01之 "目录"   RandomAccessFile RandomAccessFile 是随机访问文件(包括读/写)的类.它支持对文件随机访问的读取和写入,即我们可以从指定的位置读取/写入文件数据.需 要注意的是,RandomAccessFile 虽然属于java.io包,但它不是Inpu

java io系列21之 InputStreamReader和OutputStreamWriter

InputStreamReader和OutputStreamWriter 是字节流通向字符流的桥梁:它使用指定的 charset 读写字节并将其解码为字符.InputStreamReader 的作用是将"字节输入流"转换成"字符输入流".它继承于Reader.OutputStreamWriter 的作用是将"字节输出流"转换成"字符输出流".它继承于Writer. 转载请注明出处:http://www.cnblogs.com/

java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

本章,我们对java 管道进行学习. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_04.html java 管道介绍 在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流.它们的作用是让多线程可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用.使 用管道通信时,大致的流程是:我们在线程A中向PipedOut

java io系列17之 System.out.println(&quot;hello world&quot;)原理

我们初学java的第一个程序是"hello world" 1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("hello world"); 4 } 5 } 上面程序到底是怎么在屏幕上输出"hello world"的呢?这就是本来要讲解的内容,即System.out.println("hello world

java io系列15之 DataOutputStream(数据输出流)的认知、源码和示例

本章介绍DataOutputStream.我们先对DataOutputStream有个大致认识,然后再深入学习它的源码,最后通过示例加深对它的了解. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_15.html DataOutputStream 介绍 DataOutputStream 是数据输出流.它继承于FilterOutputStream.DataOutputStream 是用来装饰其它输出流,将DataOutputStream和DataI

java io系列22之 FileReader和FileWriter

FileReader 是用于读取字符流的类,它继承于InputStreamReader.要读取原始字节流,请考虑使用 FileInputStream.FileWriter 是用于写入字符流的类,它继承于OutputStreamWriter.要写入原始字节流,请考虑使用 FileOutputStream. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_22.html 更多内容请参考:java io系列01之 "目录"   FileRea

java io学习(五) 序列化总结(Serializable 和 Externalizable)

本章,我们对序列化进行深入的学习和探讨.学习内容,包括序列化的作用.用途.用法,以及对实现序列化的2种方式Serializable和Externalizable的深入研究. 1. 序列化是的作用和用途 序列化,就是为了保存对象的状态:而与之对应的反序列化,则可以把保存的对象状态再读出来. 简言之:序列化/反序列化,是Java提供一种专门用于的保存/恢复对象状态的机制. 一般在以下几种情况下,我们可能会用到序列化: a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候: b)当你想用套接