即使你沒有用過對象序列化(serialization),你可能也知道它。但你是否知道 Java 還支持另外一種形式的對象持久化,外部化(externalization)?
下面是序列化和外部化在代碼級的關聯方式:
public interface Serializable {}
public interface Externalizable extends Serializable {
void readExternal(ObjectInput in);
void writeExternal(ObjectOutput out);
}
序列化和外部化的主要區別
外部化和序列化是實現同一目標的兩種不同方法。下面讓我們分析一下序列化和外部化之間的主要區別。
通過Serializable接口對對象序列化的支持是內建於核心 API 的,但是java.io.Externalizable的所有實現者必須提供讀取和寫出的實現。Java 已經具有了對序列化的內建支持,也就是說只要製作自己的類java.io.Serializable,Java 就會試圖存儲和重組你的對象。如果使用外部化,你就可以選擇完全由自己完成讀取和寫出的工作,Java 對外部化所提供的唯一支持是接口:
voidreadExternal(ObjectInput in)
void writeExternal(ObjectOutput out)
現在如何實現readExternal() 和writeExternal() 就完全看你自己了。
序列化會自動存儲必要的信息,用以反序列化被存儲的實例,而外部化則只保存被存儲的類的標識。當你通過java.io.Serializable接口序列化一個對象時,有關類的信息,比如它的屬性和這些屬性的類型,都與實例數據一起被存儲起來。在選擇走Externalizable這條路時,Java 只存儲有關每個被存儲類型的非常少的信息。
每個接口的優點和缺點
Serializable接口
? 優點:內建支持
? 優點:易於實現
? 缺點:佔用空間過大
? 缺點:由於額外的開銷導致速度變比較慢
Externalizable接口
? 優點:開銷較少(程序員決定存儲什麼)
? 優點:可能的速度提升
? 缺點:虛擬機不提供任何幫助,也就是說所有的工作都落到了開發人員的肩上。
在兩者之間如何選擇要根據應用程序的需求來定。Serializable通常是最簡單的解決方案,但是它可能會導致出現不可接受的性能問題或空間問題;在出現這些問題的情況下,Externalizable可能是一條可行之路。
要記住一點,如果一個類是可外部化的(Externalizable),那麼Externalizable方法將被用於序列化類的實例,即使這個類型提供了Serializable方法:
private void writeObject()
private void readObject()
Externalizable 實例
被Serializable接口聲明的類的對象的內容都將被序列化,如果現在用戶希望自己指定序列化的內容,則可以讓一個類實現Externalizable接口,此接口定義如下:
- public interface Externalizable extends Serializable {
- public void writeExternal(ObjectOutput out) throws IOException ;
- public void readExternal(ObjectInput in) throws IOException,
- ClassNot FoundException ;
- }
Externalizable接口是Serializable接口的子接口,在此接口中定義了兩個方法,這兩個方法的作用如下。
writeExternal(ObjectOutput out):在此方法中指定要保存的屬性信息,對象序列化時調用。
readExternal(ObjectInput in):在此方法中讀取被保存的信息,對象反序列化時調用。
這兩個方法的參數類型是ObjectOutput和ObjectInput,兩個接口的定義如下。
ObjectOutput接口定義:
- public interface ObjectOutput extends DataOutput
ObjectInput接口定義:
- public interface ObjectInput extends DataInput
可以發現以上兩個接口分別繼承DataOutput和DataInput,這樣在這兩個方法中就可以像DataOutputStream和DataInputStream那樣直接輸出和讀取各種類型的數據。
如果一個類要使用Externalizable實現序列化時,在此類中必須存在一個無參構造方法,因爲在反序列化時會默認調用無參構造實例化對象,如果沒有此無參構造,則運行時將會出現異常,這一點的實現機制與Serializable接口是不同的。
範例:修改Person類並實現Externalizable接口
- package org.lxh.demo12.serdemo;
- import java.io.Externalizable;
- import java.io.IOException;
- import java.io.ObjectInput;
- import java.io.ObjectOutput;
- public class Person implements Externalizable {// 此類的對象可以被序列化
- private String name; // 聲明name屬性
- private int age; // 聲明age屬性
- public Person(){} // 必須定義無參構造
- public Person(String name, int age) { // 通過構造方法設置屬性內容
- this.name = name;
- this.age = age;
- }
- public String toString() { // 覆寫toString()方法
- return "姓名:" + this.name + ";年齡:" + this.age;
- }
- // 覆寫此方法,根據需要讀取內容,反序列化時使用
- public void readExternal(ObjectInput in) throws IOException,
- ClassNotFoundException {
- this.name = (String)in.readObject() ; // 讀取姓名屬性
- this.age = in.readInt() ; // 讀取年齡
- }
- // 覆寫此方法,根據需要可以保存屬性或具體內容,序列化時使用
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeObject(this.name) ; // 保存姓名屬性
- out.writeInt(this.age) ; // 保存年齡屬性
- }
- }
以上程序中的Person類實現了Externalizable接口,這樣用戶就可以在類中有選擇地保存需要的屬性或者其他的具體數據。在本程序中,爲了與之前的程序統一,將全部屬性保存下來。
範例:序列化和反序列化Person對象
- package org.lxh.demo12.serdemo;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.InputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.OutputStream;
- public class SerDemo03 {
- public static void main(String[] args) throws Exception {
- ser(); // 序列化
- dser(); // 反序列化
- }
- public static void ser() throws Exception { // 序列化操作
- File f = new File("D:" + File.separator + "test.txt");
- ObjectOutputStream oos = null;
- OutputStream out = new FileOutputStream(f); // 文件輸出流
- oos = new ObjectOutputStream(out); // 爲對象輸出流實例化
- oos.writeObject(new Person("張三", 30)); // 保存對象到文件
- oos.close(); // 關閉輸出
- }
- public static void dser() throws Exception { // 反序列化操作
- File f = new File("D:" + File.separator + "test.txt");
- ObjectInputStream ois = null;
- InputStream input = new FileInputStream(f); // 文件輸出流
- ois = new ObjectInputStream(input); // 爲對象輸出流實例化
- Object obj = ois.readObject(); // 讀取對象
- ois.close(); // 關閉輸出
- System.out.println(obj);
- }
- }
從以上代碼中可以發現,使用Externalizable接口實現序列化明顯要比使用Serializable接口實現序列化麻煩得多,除此之外,兩者的實現還有不同,如表12-27所示。
表12-27 Externalizable接口與Serializable接口實現序列化的區別
序 號 |
區 別 |
Serializable |
Externalizable |
1 |
實現複雜度 |
實現簡單,Java對其 有內建支持 |
實現複雜, 由開發人員自己完成 |
2 |
執行效率 |
所有對象由Java統一保存, 性能較低 |
開發人員決定哪個對象保存, 可能造成速度提升 |
3 |
保存信息 |
保存時佔用空間大 |
部分存儲, 可能造成空間減少 |
在一般的開發中,因爲Serializable接口的使用較爲方便,所以出現較多