1. Java序列化和反序列化(What)
Java序列化(Serialize)是指將一個Java對象寫入IO流中;
Java反序列化(Deserialize)指的是從IO流中回復(fù)IO對象。
2. 序列化的意義(Why)
序列化機(jī)制可以將Java對象轉(zhuǎn)換為數(shù)據(jù)流用來保存在磁盤上或者通過網(wǎng)絡(luò)傳輸。這使得對象可以脫離程序獨(dú)立存在。
3. 如何進(jìn)行序列化(How)
為了使對象支持序列化機(jī)制,需要讓它的類變成可序列化的(serializable)。通過實現(xiàn)兩個接口之一實現(xiàn):
- Serializable
- Externalnalizable
3.1 序列化的步驟
實現(xiàn)了Serializable接口的類,可以通過兩個步驟序列化該對象:
- 創(chuàng)建建立在其他節(jié)點(diǎn)流上的ObjectOutputStream
//創(chuàng)建ObjectOutputStream輸出流
ObjectOutputStream oos = new ObejctOutputStream(new FileOutputStream("object.txt"));
- 調(diào)用ObjectOutputStream對象的writeObject()方法輸出可序列化對象
//將一個Person對象輸出到輸出流中
oos.writeObject(per);
3.2 序列化的代碼
- 定義一個Person類,實現(xiàn)了Serializable接口,標(biāo)識該類的對象是可序列化的。
public class Person implements java.io.Serializable {
private String name;
private int age;
// 這里沒有無參構(gòu)造器
public Person(String name, int age){
this.name = name;
this.age = age;
}
// name和age的setter和getter方法
...
}
- 將Person對象寫入硬盤
import java.io.*;
public class Test{
public static void main(String[] args){
try{
// 創(chuàng)建ObjectOutputStream輸出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
Person per = new Person("Junzerg", 20);
// 將per對象寫入輸出流
oos.writeObject(per);
}
catch (IOException ex){
ex.printStackTrace();
}
}
}
3.3 序列化結(jié)果
AC ED 00 05 73 72 00 06 50 65 72 73 6F 6E 2A 98
15 B9 5C 2E C1 6C 02 00 02 49 00 03 61 67 65 4C
00 04 6E 61 6D 65 74 00 12 4C 6A 61 76 61 2F 6C
61 6E 67 2F 53 74 72 69 6E 67 3B 78 70 00 00 00
14 74 00 07 4A 75 6E 7A 65 72 67
共75字節(jié)。
3.4 序列化的字節(jié)數(shù)
把Person類的age屬性設(shè)置為Long,重新序列化,結(jié)果為:
AC ED 00 05 73 72 00 06 50 65 72 73 6F 6E BE CF
78 98 E0 A3 0B E9 02 00 02 4A 00 03 61 67 65 4C
00 04 6E 61 6D 65 74 00 12 4C 6A 61 76 61 2F 6C
61 6E 67 2F 53 74 72 69 6E 67 3B 78 70 00 00 00
00 00 00 00 14 74 00 07 4A 75 6E 7A 65 72 67
共79字節(jié)。
4. 反序列化
4.1 反序列化的步驟
- 創(chuàng)建一個建立在其他節(jié)點(diǎn)流基礎(chǔ)上的ObjectInputStream輸入流
// 出啊構(gòu)建一個ObjectInputStream輸入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Object.txt"));
- 調(diào)用ObjectInputStream對象的readObject方法讀取流中的對象,該方法返回一個Java對象,再強(qiáng)制轉(zhuǎn)換為真實類型。
//從輸入流中讀取一個Java對象并將其強(qiáng)制類型轉(zhuǎn)換成Person類
Person p = (Person) ois.readObject();
4.2 反序列化的代碼
從前文創(chuàng)建的Object.txt中讀取Person類
import java.io.*;
public class ReadObject{
public static void main(String[] args){
try{
// 創(chuàng)建一個ObjectInputStream輸入流
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("Object.txt"));
// 從輸入流中讀取一個Java對象,轉(zhuǎn)換為Person類
Person p = (Person)ois.readObject();
System.out.println("name: " + p.getName()
+ "\n age: " + p.getAge());
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
4.3 反序列化的結(jié)果
name: Junzerg
age: 20
5 對象引用的序列化和序列化算法
5.1 對象引用的序列化
如果要序列化的類的某個成員變量是一個非String類型的引用類型,那么這個引用類型必須是可序列化的。
例如有一個Teacher類持有Person類的引用
public class Teacher implements Seializable{
private String name;
Private Person student;
public Teacher(String name, Person student){
this.name = name;
this.student = student;
}
// 省略name和student的stter和getter方法
...
}
為了在反序列化Teacher對象時正確恢復(fù),Person類必須也是可序列化的,否則Teacher不可序列化
5.2 多個實例變量引用同一個引用對象的特殊情況
當(dāng)兩個Teacher對象引用同一個Person對象的時候:
Person per = new Person("Junzerg", 20);
Teacher t1 = new Teacher("Miss Li", per);
Teacher t2 = new Teacher("Mr Wu", per);
在程序依次序列化三個對象的過程中,看起來似乎會向輸出流中輸出三個Person對象。
這時當(dāng)程序從輸入流中反序列化這些對象時,就會得到三個Person對象,這樣這樣t1和t2引用的就不是同一個Person對象了。
5.3 Java序列化算法
為了避免5.2中出現(xiàn)的錯誤,Java的序列化算法如下:
- 所有保存在磁盤中的對象都有一個序列化編號
- 當(dāng)程序試圖序列化一個對象時,程序會先檢查該對象是否已經(jīng)被序列化過,只有改對象從未(在本次虛擬機(jī)中)被序列化過,系統(tǒng)才會將給對象轉(zhuǎn)換成字節(jié)序列并出輸出。
- 如果某個對象已經(jīng)被序列化過,程序?qū)⒅苯映鰰粋€序列化編號,而不是重新序列化該對象。
6. 自定義序列化
6.1 遞歸序列化
當(dāng)對某個對象及進(jìn)行序列化時,系統(tǒng)自動把該對象的所有實例變量依次進(jìn)行序列化,如果某個實例變量引用另一個對象,則被引用的變量也會被序列化,這種情況被稱為遞歸序列化。
6.2 transient關(guān)鍵字
在遞歸序列化的過程中,可能遇到不想被序列化或者不能被序列化的變量。這時可以使用transient關(guān)鍵字在序列化時忽略該變量,避免引發(fā)java.io.NotSerializableException異常。
6.3 transient關(guān)鍵字的使用
- 有帶有transient關(guān)鍵字修飾的變量的Person類
public class Person implements java.io.Serializable {
private String name;
private transient int age;
// 這里沒有無參構(gòu)造器
public Person(String name, int age){
this.name = name;
this.age = age;
}
// name和age的setter和getter方法
...
}
注意:transient關(guān)鍵字只能用于修飾實例變量,不可修飾Java程序中的其他部分。
- Person對象的序列化和反序列化。
public class TransientTest{
public static void main(String[] args){
try{
// 創(chuàng)建ObjectOutputStream輸出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("transient.txt"));
//創(chuàng)建ObjectInputStream輸入流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("transient.txt"));
Person per = new Person("Junzerg", 20);
// 將per對象序列化輸出
oos.writeObject(per);
Person p = (Person) ois.readObject();
System.out.println(p.getAge());
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
- 輸出結(jié)果
3.1 控制臺輸出:
0
3.2 transient.txt內(nèi)容為:
AC ED 00 05 73 72 00 06 50 65 72 73 6F 6E DB F9
DD 8C 83 99 C5 2E 02 00 01 4C 00 04 6E 61 6D 65
74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74
72 69 6E 67 3B 78 70 74 00 07 4A 75 6E 7A 65 72
67
大小為65字節(jié)。
可以看到per實例中的age變量并沒有序列化。