Wednesday, February 6, 2013

[JAVA] 序列化的處理Serialization

序列化的處理




「序列化(Serialization)」是將物件寫入到串流的程式。而「反序列化(Deserialization)」則是將串流讀出來的程序。我們要透過ObjectInputStream和ObjectOutputStream類別來處理序列化和反序列化的工作,該類別分別衍生自InputStream和OutputStream類別。如果您要將某個類別做序列化的處理,該類別必需實作Serializable介面,但該介面只是一個標記介面,並沒有定義任何的成員。ObjectInputStream類別的建構子為:



建構子

ObjectInputStream()
作 用
建構ObjectInputStream物件


建構子
ObjectInputStream(InputStream in)
作 用
建構ObjectInputStream物件,以便由in物件讀取



而ObjectOutputStream類別的建構子為:



建構子

ObjectOutputStream()
作 用

建構ObjectOutputStream物件



建構子
ObjectOutputStream(OutputStream out)

作 用

建構ObjectOutputStream物件,以便由寫入out物件



以下的範例示範如何設計需要序列化的類別:



程式10-18:Chap 10\ Employee.java

01: import java.io.*;

02:

03: public class Employee implements Serializable {

04: private String name;

05: private String ID;

06: private float salary;

07:

08: Employee(String name , String ID , float salary) {

09: this.name = name;

10: this.ID = ID;

11: this.salary = salary;

12: }

13: public String getName(){

14: return name;

15: }

16: public String getID() {

17: return ID;

18: }

19: public float getSalary() {

20: return salary;

21: }

22: }

Employee類別是需要序列化的類別。注意程式的第3行,該行實作了Serializable介面。除了這一行之外,Employee類別和一般的類別寫作並沒有特別需要注意的地方。程式的4~7行定義了3個私有資料成員。程式的第8~11行宣告了一個建構子,用來初始化私有資料成員。而第13~21行的程式則分別顯示出各私有成員的內容。定義好了需要序列化的類別之後,我們再來看一下如何將Employee物件序列化並寫入檔案之中:

程式10-19:Chap 10\ WriteData.java

01: import java.io.*;

02:

03: public class WriteData {

04: public static void main(String[] args) {

05: Employee[] s = new Employee[2];

06:

07: s[0] = new Employee(“Alex” ,“001” , 32000.f);

08: System.out.println(s[0].getName());

09: System.out.println(s[0].getID());

10: System.out.println(s[0].getSalary());

11:

12: s[1] = new Employee(“Ivy”,“002”, 43000.f);

13: System.out.println(s[1].getName());

14: System.out.println(s[1].getID());

15: System.out.println(s[1].getSalary());

16:

17: try{

18: FileOutputStream fs = new FileOutputStream(“Employee.txt”);

19: ObjectOutputStream out = new ObjectOutputStream(fs);

20: out.writeObject(s);

21: out.close();

22: fs.close();

23: } catch (IOException e){

24: System.out.println(e.toString());

25: }

26: }

27: }

程式的執行結果為:



Alex

001

32000.0

Ivy

002

43000.0

程式執行後,您會在目錄下找到「Employee.txt」文字檔,但您無法直接瀏覽該檔的內容。範例程式的第5行宣告一個Employee陣列,可用來儲存2筆資料。而程式的第7~15行分別建立Employee物件,並且將物件的內容顯示在螢幕上。程式的第18~22行是本範例程式的重點。第18行宣告了一個FileOutputStream物件,用來將內容寫入Employee.txt檔案中。再於第19行程式中將FileOutputStream物件傳遞給ObjectOutputStream,並建立新的ObjectOutputStream物件。第20行程式中,使用ObjectOutputStream物件的WriteObject方法將Employee陣列的內容寫入Employee.txt檔案中。寫入的方式很簡單,但請注意,程式中寫入Employee陣列的資料,因此,您在讀取資料時,也必需將檔案的內容輸入Employee陣列中,否則會因型態不符而產生classCastException的例外。



下列的範例程式示範如何將Employee.txt的內容讀出:



程式10-20:Chap 10\ ReadData.java

01: import java.io.*;

02:

03: public class ReadData {

04: public static void main(String[] args) {

05: try{

06: FileInputStream fs = new FileInputStream(“Employee.txt”);

07: ObjectInputStream in = new ObjectInputStream(fs);

08:

09: Employee[] s = (Employee[] ) in.readObject();

10:

11: for(int i = 0 ; i
12: System.out.println(s[i].getName());

13: System.out.println(s[i].getID());

14: System.out.println(s[i].getSalary());

15: }

16: in.close();

17: fs.close();

18: } catch (Exception e) {

19: System.out.println( e.toString());

20: }

21: }

22: }

程式的執行結果為:



Alex

001

32000.0

Ivy

002

43000.0

範例程式的第6行宣告了一個FileInputStream物件指定需要讀取的檔案。而如果需要讀取已序列化的資料,我們需要使用ObjectInputStream物件。因此,程式的第7行將FileInputStream物件傳遞給ObjectInputStream並建立ObjectInputStream物件。第9行宣告Employee陣列用來讀取Employee.txt的內容。該行程式中使readObject方法來讀取資料,但因為Employee.txt存入的資料是Employee陣列,所以,在使用readObject方法讀取後,需要再將讀取的內容轉為Employee陣列。程式第11~14行則顯示出讀取的內容。



當您在宣告類別時,如果實作了Serializable介面,您也可以選擇性的將資料成員做序列化的處理。您可以在不希望被序列化的資料成員前加入transient修飾字。例如:如果您將Employee.java的資料成員修改為:



private String name;

private String ID;

private transient float salary;

現在,salary資料成員加上了transient修飾字。請依次再執行WriteData和ReadData這兩支程式,



您會發現ReadData的執行結果變成了:



Alex

001

0.0

Ivy

002

0.0

這是因為在序列化的過程式,並沒有將salary成員的資料寫入,因此,在讀出資料時,並沒有salary的資料,而是以預設值來顯示。