1. 什么是Serialization?
串行化(Serialization)是計算機科學中的一個概念,它是指將對象存儲到介質(如文件、內在緩沖區等)中或是以二進制方式通過網絡傳輸。之后可以通過反串行化從這些連續的位數據重新構建一個與原始對象狀態相同的對象,因此在特定情況下也可以說是得到一個副本,但并不是所有情況都這樣。
Java有Serialization API為開發者提供了一種標準的機制來串行化類。
2. 為什么要Serilzation?
特別地,串行化主要有三種用途:
1)作為一種持久化機制
如果使用的是FileOutputStream流的方式,則數據將被自動地寫入文件中,
2)作為一種復制機制
如果使用的是ByteArrayOutputStream
流的方式,數據將寫入內存中的字節數組中。該字節數組可以用來創建初始對象的副本,
3)作為一種通信機制
如果是使用套接字(Socket)流的方式,則數據自動地通過網絡連接傳輸一另一個端點,并由這個端點上的程序來決定做什么。
3. Serialization的基本用法:默認機制
將要串行化的類必須實現java.io.Serializable接口,或者是繼承實現了該接口的類。然后通過java.io.ObjectOutputStream類來實現持久化,如果用保存到文件上還需要用到java.io.FileOutputStream類。因為ObjectOutputStream被認為是java.io包中的高級類所以可用它來包裝低級的類FileOutputStream。在持久化過程中調用的一個方法是ObjectOutputStream對象的writeObject(obj)方法。
當要從文件中恢復對象時,則是使用java.io.OjbectInputStream與FileInputStream類,調用一方法是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常見問題
正如前面提到的,所有可串行化的類必須直接或是通過繼承方式間接地實現java.io.Serializable接口,由于Object類關沒有實現這個接口,所以并不是所有類的對象都是可串行化的。像AWT與Swing的GUI組件、字符串、數組等都是可串行化的,而像一些系統級的類(Thread,OutputStream
等)和Socket類是不可串行化的。
問題一:如果在一個可串行化的類中Has-As不可串行化的類該怎么處理?
在這種情況下在運行時會拋出NotSerializableException。
為了解決類似問題,Java中提供了transient關鍵字來跳過對不可串行化類的對象的處理。但這依然可能會引起一些問題,在反串行化時,被標識為transient變量不會恢復到其原始狀態,而是提供默認值,如示例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;
}
}
問題二:如果父類不可串行化,子類實現了Serializable會怎樣?
如果有一個Animal類是不可串行化的,而有一個Dog類繼承自Animal類并且實現了Serializabl接口,則沒有串行化時沒有任何問題,但是在反串行化時將會重新調用Animal的構造函數,如示例3所示。
示例3的運行結果如下:
1> No Color - new Dog
2> Green - My Dog
4> No Color - My Dog
因為Animal不可串行化,所以必須運行構造函數,但不會在實現Serializable的反串行化類上運行構造函數。
示例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();