1. 什么是Serialization?
串行化(Serialization)是計算機科學中的一個概念,它是指將對象存儲到介質(zhì)(如文件、內(nèi)在緩沖區(qū)等)中或是以二進制方式通過網(wǎng)絡傳輸。之后可以通過反串行化從這些連續(xù)的位數(shù)據(jù)重新構建一個與原始對象狀態(tài)相同的對象,因此在特定情況下也可以說是得到一個副本,但并不是所有情況都這樣。
Java有Serialization API為開發(fā)者提供了一種標準的機制來串行化類。
2. 為什么要Serilzation?
特別地,串行化主要有三種用途:
1)作為一種持久化機制
如果使用的是FileOutputStream流的方式,則數(shù)據(jù)將被自動地寫入文件中,
2)作為一種復制機制
如果使用的是ByteArrayOutputStream
流的方式,數(shù)據(jù)將寫入內(nèi)存中的字節(jié)數(shù)組中。該字節(jié)數(shù)組可以用來創(chuàng)建初始對象的副本,
3)作為一種通信機制
如果是使用套接字(Socket)流的方式,則數(shù)據(jù)自動地通過網(wǎng)絡連接傳輸一另一個端點,并由這個端點上的程序來決定做什么。
3. Serialization的基本用法:默認機制
將要串行化的類必須實現(xiàn)java.io.Serializable接口,或者是繼承實現(xiàn)了該接口的類。然后通過java.io.ObjectOutputStream類來實現(xiàn)持久化,如果用保存到文件上還需要用到java.io.FileOutputStream類。因為ObjectOutputStream被認為是java.io包中的高級類所以可用它來包裝低級的類FileOutputStream。在持久化過程中調(diào)用的一個方法是ObjectOutputStream對象的writeObject(obj)方法。
當要從文件中恢復對象時,則是使用java.io.OjbectInputStream與FileInputStream類,調(diào)用一方法是ObjectInputStream對象的readObject()方法。
示例1:
import java.io.*;
public class Cat implements Serializable {
private String name;
public Cat () {
this.name = "new cat";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.*;
public class CatDemo {
public static void main(String[] args) {
Cat cat = new Cat();
try { //串行化
FileOutputStream fos = new FileOutputStream("catDemo.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
System.out.println(" 1> " + cat.getName());
cat.setName("My Cat");
oos.writeObject(cat);
oos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
try { //反串行化
FileInputStream fis = new FileInputStream("catDemo.out");
ObjectInputStream ois = new ObjectInputStream(fis);
cat = (Cat) ois.readObject();
System.out.println(" 2> " + cat.getName());
ois.close();
} catch (IOException ex) {
ex.printStackTrace();
}catch(ClassNotFoundException ex) {
ex.printStackTrace();
}
}
}
4. Serialization常見問題
正如前面提到的,所有可串行化的類必須直接或是通過繼承方式間接地實現(xiàn)java.io.Serializable接口,由于Object類關沒有實現(xiàn)這個接口,所以并不是所有類的對象都是可串行化的。像AWT與Swing的GUI組件、字符串、數(shù)組等都是可串行化的,而像一些系統(tǒng)級的類(Thread,OutputStream
等)和Socket類是不可串行化的。
問題一:如果在一個可串行化的類中Has-As不可串行化的類該怎么處理?
在這種情況下在運行時會拋出NotSerializableException。
為了解決類似問題,Java中提供了transient關鍵字來跳過對不可串行化類的對象的處理。但這依然可能會引起一些問題,在反串行化時,被標識為transient變量不會恢復到其原始狀態(tài),而是提供默認值,如示例2中的pig引用將賦值為null,age變量賦值為0;
附:基本類型和引用類型的默認值
對象引用:null
byte, short, int, long :0
float, double:0.0
boolean:false
char:'\u0000'(這是Unicode字符集的空格)
示例2:
import java.io.*;
public class NewPig2 implements Serializable {
private String newName;
private transient Pig pig = new Pig();
private transient int age = 2;
public NewPig2() {
newName = "new Pig 2";
//pig = new Pig();
}
public Pig getPig() {
return this.pig;
}
public void setName(String name) {
this.newName = name;
}
public String getName() {
return this.newName;
}
public int getAge() {
return this.age;
}
}
問題二:如果父類不可串行化,子類實現(xiàn)了Serializable會怎樣?
如果有一個Animal類是不可串行化的,而有一個Dog類繼承自Animal類并且實現(xiàn)了Serializabl接口,則沒有串行化時沒有任何問題,但是在反串行化時將會重新調(diào)用Animal的構造函數(shù),如示例3所示。
示例3的運行結果如下:
1> No Color - new Dog
2> Green - My Dog
4> No Color - My Dog
因為Animal不可串行化,所以必須運行構造函數(shù),但不會在實現(xiàn)Serializable的反串行化類上運行構造函數(shù)。
示例3:
public class Animal {
private String color;
public Animal () {
this.color = "No Color";
}
public void setColor(String color) {
this.color = color;
}
public String getColor () {
return this.color;
}
}
import java.io.*;
public class Dog extends Animal implements Serializable {
private String name;
public Dog () {
this.name = "new Dog";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.*;
public class DogTest {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(" 1> " + dog.getColor() + " - " + dog.getName());
dog.setColor("Green");
dog.setName("My Dog");
System.out.println(" 2> " + dog.getColor() + " - " + dog.getName());
try {//串行化
FileOutputStream fos = new FileOutputStream("myDog.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(dog);
oos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
try {//反串行化
FileInputStream fis = new FileInputStream("myDog.out");
ObjectInputStream ois = new ObjectInputStream(fis);
dog = (Dog) ois.readObject();
System.out.println(" 4> " + dog.getColor() + " - " + dog.getName());
ois.close();
} catch (Exception ex) {
ex.printStackTrace();