
序列化:指數(shù)據(jù)持久化,永久性的將數(shù)據(jù)保存在磁盤的過程。反序列化:將數(shù)據(jù)從磁盤中讀取出來并恢復為原對象的一個過程。在 Android 中序列化分為兩種分別是Serializable、Parcelable。其中Parcelable性能較高,是 Google 針對 Android 系統(tǒng)專門推出的一種高性能序列化方式,Serizlizable使用起來較為簡單我們只需實現(xiàn)它既可,性能相對較低。下面開始我們的分析。
Serializable
Serializable是 Java 自帶的一種序列化方式,它是一個空接口,為對象提供了標準的序列化和反序列化操作。使用Serializable來實現(xiàn)序列化非常簡單,我們只需在當前類中實現(xiàn)Serializable接口即可,如下。
public class User implements Serializable {
private static final long serialVersionUID = 2479012378928432xxxL;
public int userId;
public String userName;
public boolean isMale;
}
以上我們就可以實現(xiàn)對象的序列化和反序列化操作了,如果一個對象只涉及到序列化,其serialVersionUID非必須聲明字段,也就是說我們只需實現(xiàn)Serializable接口即可,其他工作系統(tǒng)已經(jīng)幫我們自動實現(xiàn),是不是超級簡單。
通過Serializable方式來實現(xiàn)對象的序列化非常簡單,幾乎所有工作都被系統(tǒng)自動完成了。如何實現(xiàn)序列化和反序列化也非常簡單,只需要使用ObjectOutputStream和ObjectInputStream即可輕松實現(xiàn)。下面我們看個例子。
//序列化過程
User user = new User(0, "jake", true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化過程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();
上述代碼演示了采用Serializable方式序列化對象的典型過程,很簡單,只需要把實現(xiàn)了Serializable接口的 User 對象寫到文件中就可以快速恢復了,恢復后的對象newUser和 user 的內(nèi)容完全一樣,但兩者并不是同一個對象。
上面我們提到了serialVersionUID可指定可不指定,那到底要不要指定呢?如果指定的話,serialVersionUID后面那一長串數(shù)字有時什么含義呢?我們要明白,系統(tǒng)既然提供了這個serialVersionUID,那么它必然是有用的。這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當前類的serialVersionUID相同才能夠正常地被反序列化。
serialVersionUID的詳細工作機制是這樣的:序列化的時候系統(tǒng)會把當前類的serialVersionUID寫入序列化的文件中(也可能是其他中介),當反序列化的時候系統(tǒng)會去檢測文件中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化;否則就說明當前類和序列化的類相比發(fā)生了某些變換,比如成員變量的數(shù)量、類型可能發(fā)生了改變,這個時候是無法正常反序列化的,因此會報如下錯誤:
java.io.InvalidClassException:Main;local class incompatible:stream classdesc serialVersionUID = 2380913098234342xxxxL,local class serialVersionUID = 23io23423iou423xxxxL。
一般來說,我們應該手動指定serialVersionUID的值,比如1L,也可以讓 Eclipse 根據(jù)當前類的結構自動去生成它的 hash 值,這樣序列化和反序列化時兩者的serialVersionUID是相同的,因此可以正常進行反序列化。如果不手動指定serialVersionUID的值,反序列化時當前類有所改變,比如增加或刪除了某些成員變量,那么系統(tǒng)就會重新計算當前類的 hash 值并把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和序列化的數(shù)據(jù)中的serialVersionUID不一致,于是反序列失敗,程序就會 crash。
以上,我們可以明顯感覺到serialVersionUID的作用,當我們手動指定了它以后,就可以在很大程度上避免反序列化過程的失敗。比如當版本升級后,我們可能刪除了某個成員變量也可能增加了一些新的成員變量,這個時候我們的反序列化過程仍然能夠成功,程序仍然能夠最大限度地恢復數(shù)據(jù)。相反,如果不指定serialVersionUID的話,程序則會掛掉。
當然我們還要考慮另外一種情況,如果類結構發(fā)生了非常規(guī)性改變,比如修改了類名,修改了成員變量的類型,這個時候盡管serialVersionUID驗證通過了,但是反序列化過程還是會失敗,因為類結構有了毀滅性的改變,根本無法從老版本的數(shù)據(jù)中還原出一個新的類結構對象。
根據(jù)上面的分析,我們可以知道,給serialVersionUID指定為1L或者采用 Eclipse 根據(jù)當前類結構去生成的 hash 值,這兩者并沒有本質區(qū)別,效果完全一樣。以下是兩點需要特別提一下,首先靜態(tài)成員變量屬于類不屬于對象,所以不會參與序列化過程;其次用transient關鍵字標記的成員變量不參與序列化過程。
另外,系統(tǒng)的默認序列化過程也是可以改變的,通過實現(xiàn)如下兩個方法即可重寫系統(tǒng)默認的序列化和反序列化過程,具體怎么去重寫這兩個方法就是很簡單的事了,這里就不再詳細介紹,畢竟大部分情況下我們不需要重寫這兩個方法。
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
//write 'this' to 'out'...
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
//populate the fields of 'this' from the data in 'in'...
}
Parcelable
上面我們通過了Serializable來實現(xiàn)序列化,這里我們接著介紹另一種序列化方式:Parcelable。Parcelable也是一個接口,只要實現(xiàn)這個接口同時完善相關函數(shù)這個類的對象就可以實現(xiàn)序列化。下面的示例是一個典型的用法。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User (int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents () {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
這里先說一下 Parcel,Parcel 內(nèi)部包裝了可序列化的數(shù)據(jù)。從上述代碼中可以看出,在序列化過程中需要實現(xiàn)的功能有序列化、反序列化和內(nèi)容描述。序列化功能由writeToParcel方法來完成,最終是通過 Parcel 中的一系列 write 方法來完成的;反序列化功能由CREATOR完成。其內(nèi)部標明了如何創(chuàng)建序列化對象和數(shù)組,并通過 Parcel 的一系列 read 方法來完成反序列化過程;內(nèi)容描述由describeContents方法來完成返回1。
注意:在User(Parcel in)方法中,由于 book 是另一個可序列化的對象,所以它的反序列化過程需要傳遞當前線程的上下文類加載器,否則會報無法找到類的錯誤。詳細的方法說明見下表。
| 方法 | 功能 | 標記位 |
|---|---|---|
| createFromParcel(Parcel in) | 從序列化后的對象中創(chuàng)建原始對象 | |
| newArray(int size) | 創(chuàng)建指定長度的原始對象數(shù)組 | |
| User(Parcel in) | 從序列化后的對象中創(chuàng)建原始對象 | |
| writeToParcel (Parcel out, int flags) | 將當前對象寫入序列化結構中,其中 flags 標識有兩種值:0 或者 1(參見右側標記位)。為 1 時標識當前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況都為 0 | PARCELABLE_WRITE_RETURN_VALUE |
| describeContents | 返回當前對象的內(nèi)容描述。如果含有文件描述符,返回 1 (參見右側標記位),否則返回 0,機會所有情況都返回 0 | CONTENTS_FILE_DESCRIPTOR |
系統(tǒng)已經(jīng)為我們提供了許多實現(xiàn)了Parcelable接口的類,它們都是可以直接序列化的,比如 Intent、Bundle、Bitmap等,同時 List 和 Map 也可以序列化,前提是它們里面的每個元素都是可序列化的。
既然Parcelable和Serializable都能實現(xiàn)序列化,那么二者該如何選取呢?Serializable是 Java 中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量 I/O 操作。而Parcelable是 Android 中的序列化方式,因此是 Android 推薦的序列化方式,因此我們要首選Parcelable。Parcelable主要用在內(nèi)存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化后通過網(wǎng)絡傳輸也都是可以的,但是這個過程稍顯復雜,因此在這兩種情況下建議大家使用Serializable。以上就是序列化的內(nèi)容了。
完~
文章參考自:Android 開發(fā)藝術
喜歡有幫助的話: 雙擊、評論、轉發(fā),動一動你的小手讓更多的人知道!關注 Android_YangKe