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 }
View Code

運行結果

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 }
View Code

運行結果

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 }
View Code

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 }
View Code

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 }
View Code

運行結果

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 }
View Code

結果是,編譯出錯!
事實證明,不能對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 }
View Code

至此,關於“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 }
View Code

運行結果

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 }
View Code

運行結果

testWrite box: [desk: (80, 48) ]
testRead  box: [desk: (80, 48) ]

至此,序列化的內容就全部講完了。更多相關的內容,請參考:

1. java 集合系列目錄(Category)

2. java io系列01之 IO框架

3. java io系列02之 ByteArrayInputStream的簡介,源碼分析和示例(包括InputStream)

4. java io系列03之 ByteArrayOutputStream的簡介,源碼分析和示例(包括OutputStream)

5. java io系列04之 管道(PipedOutputStream和PipedInputStream)的簡介,源碼分析和示例

6. java io系列05之 ObjectInputStream 和 ObjectOutputStream

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM