序列化你真的會用嗎-[Android_YangKe]

Android_YangKe.jpg

序列化:指數(shù)據(jù)持久化,永久性的將數(shù)據(jù)保存在磁盤的過程。反序列化:將數(shù)據(jù)從磁盤中讀取出來并恢復為原對象的一個過程。在 Android 中序列化分為兩種分別是SerializableParcelable。其中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)序列化和反序列化也非常簡單,只需要使用ObjectOutputStreamObjectInputStream即可輕松實現(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)序列化,這里我們接著介紹另一種序列化方式:ParcelableParcelable也是一個接口,只要實現(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 也可以序列化,前提是它們里面的每個元素都是可序列化的。

既然ParcelableSerializable都能實現(xiàn)序列化,那么二者該如何選取呢?Serializable是 Java 中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量 I/O 操作。而Parcelable是 Android 中的序列化方式,因此是 Android 推薦的序列化方式,因此我們要首選ParcelableParcelable主要用在內(nèi)存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化后通過網(wǎng)絡傳輸也都是可以的,但是這個過程稍顯復雜,因此在這兩種情況下建議大家使用Serializable。以上就是序列化的內(nèi)容了。

完~

文章參考自:Android 開發(fā)藝術

喜歡有幫助的話: 雙擊、評論、轉發(fā),動一動你的小手讓更多的人知道!關注 Android_YangKe

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 11,166評論 0 24
  • Android跨進程通信IPC整體內(nèi)容如下 1、Android跨進程通信IPC之1——Linux基礎2、Andro...
    隔壁老李頭閱讀 12,285評論 6 38
  • What? 何為序列化與反序列化?序列化:將對象轉化為二進制序列的過程反序列化:將二進制序列恢復為原始對象的過程 ...
    LilacZiyun閱讀 3,129評論 0 15
  • 概念:序列化的意思籠統(tǒng)的來說就是將對象轉化成二進制,用于在文件或者網(wǎng)絡上進行傳輸;反序列化就是相反,將序列化后的二...
    dev_journey閱讀 1,139評論 0 0
  • 很多時候,我們習慣了被提醒出門不要忘記帶鑰匙,注意安全,工作注意勞逸結合,天冷穿暖點,唯獨忘記提醒幸福,往往...
    梁早早閱讀 478評論 5 11

友情鏈接更多精彩內(nèi)容