Android序列化

序(序列化和反序列化).png

是什么蝗岖?為啥用诲宇?怎么用?——靈魂三連問

1箕母、序列化和反序列化是什么苹支?

  • 序列化:把對象轉(zhuǎn)變?yōu)樽止?jié)序列的過程稱為對象的序列化砾隅。
  • 反序列化:把字節(jié)序列恢復(fù)為對象的過程稱為對象的反序列化。

2债蜜、對象序列化的用途

  • 永久的保存對象數(shù)據(jù)(將對象數(shù)據(jù)保存在文件或者磁盤中)
  • 是對象數(shù)據(jù)能夠在網(wǎng)絡(luò)上傳輸(由于網(wǎng)絡(luò)傳輸是以字節(jié)流的方式來完成對數(shù)據(jù)的傳輸?shù)那绻。虼诵蛄谢哪康氖菍ο髷?shù)據(jù)轉(zhuǎn)換成字節(jié)流的形式)。
  • 使對象能夠在進程間進行傳遞(基礎(chǔ)類型數(shù)據(jù)除外寻定,對象類型數(shù)據(jù)必須進行序列化操作后才能進行傳輸)儒洛。
  • 在Android intent之間,基礎(chǔ)數(shù)據(jù)類型可以直接傳遞特姐,但是傳遞復(fù)雜數(shù)據(jù)類型的時候晶丘,必須進行序列化。
    序列化對象的時候只針對屬性進行序列化唐含,不針對方法序列化浅浮。

3、Android實現(xiàn)序列化的兩種方式

3.1捷枯、實現(xiàn)Serializable接口

Serializable是java提供的一個序列化接口滚秩,它是一個空接口,專門為對象提供標準的序列化和反序列化操作淮捆,使用Serializable實現(xiàn)類的序列化比較簡單郁油,只要在類聲明中實現(xiàn)Serializable接口即可本股,同時強烈建議聲明序列化標識。

3.1.1 序列化舉例
public class S_Shop implements Serializable {

    private static final long serialVersionUID = -1399695071515887643L;
    public String mShopName;
    public int mShopId;
    public String mShopPhone;
    public static int STATIC_VALUE = 100;//靜態(tài)值
    public transient int TRANSIENT_VALUE;//被transient修飾 不能序列化

    @NonNull
    @Override
    public String toString() {
        return "Serializable: mShopName is " + mShopName
                + ",mShopId is " + mShopId
                + ",mShopPhone is " + mShopPhone
                + ",STATIC_VALUE is " + STATIC_VALUE
                + ",TRANSIENT_VALUE is " + TRANSIENT_VALUE;
    }
}

執(zhí)行序列化和反序列化過程:

    public static void main(String[] args) throws IOException {
        //------------------Serializable------------------
        S_Shop shop = new S_Shop();
        shop.mShopName = "商品名";
        shop.mShopId = 2022;
        shop.mShopPhone = "15700000000";
        shop.TRANSIENT_VALUE = 1000;
      
        saveObject(shop); //序列化
        readObject();//反序列化
    }

    //序列化
    private static void saveObject(S_Shop shop) {
        ObjectOutputStream outputStream = null;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("shop.obj"));
            outputStream.writeObject(shop);
            System.out.println("write-hashCode: " + shop.hashCode());
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void readObject() {
        //反序列化
        ObjectInputStream inputStream = null;
        try {
            inputStream = new ObjectInputStream(new FileInputStream("shop.obj"));
            S_Shop shop = (S_Shop) inputStream.readObject();
            System.out.println(shop.toString());
            System.out.println("read-hashCode: " + shop.hashCode());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

執(zhí)行結(jié)果:

Serializable: mShopName is 商品名,mShopId is 2022,mShopPhone is 15700000000,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

結(jié)果看到反序列化成功桐腌,從序列化結(jié)構(gòu)中又重新生成了對象拄显,這里注意一點,類中的變量TRANSIENT_VALUE是由transient修飾的案站,不能被序列化躬审,所以反序列化時得到的是默認值。另外STATIC_VALUE由static修飾蟆盐,也不參與序列化過程

3.1.2 特殊變量序列化
  1. 靜態(tài)變量的序列化
    序列化并不保存靜態(tài)變量承边,序列化保存的是對象的狀態(tài),而靜態(tài)變量是類的狀態(tài)石挂。
  2. Transient關(guān)鍵字
    transient關(guān)鍵字的作用就是控制變量的序列化博助,在變量聲明前加上該關(guān)鍵字,可以阻止該變量序列化到文件中痹愚,在反序列化后富岳,transient變量會被設(shè)為初始值,如int型的為0里伯,對象型的為null城瞎。
  3. 父類的序列化特性
    如果子類實現(xiàn)了Serializable接口而父類沒有實現(xiàn)渤闷,那么父類不會被序列化疾瓮,但是**父類必須有默認的無參構(gòu)造方法,否則會拋出InvalidClassException異常飒箭。如下圖所示
    序列化異常

解決方案:想要將父類對象也序列化狼电,就需要讓父類也實現(xiàn)Serializable接口;如果父類不實現(xiàn)的話弦蹂,就需要有默認的無參構(gòu)造函數(shù)肩碟,并且父類的變量值都是默認聲明的值。

在父類沒有實現(xiàn)Serializable接口時凸椿,虛擬機不會序列化父對象削祈,而一個Java對象的初始化必須先初始化父對象,再初始化子對象脑漫,反序列化也不例外髓抑。所以在反序列化時,為了構(gòu)造父對象优幸,只能調(diào)用父類對象的無參構(gòu)造函數(shù)作為默認的父對象吨拍。因此當我們?nèi)「笇ο蟮淖兞恐禃r,它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值

3.1.3 序列化舉例序列化步驟:
  • 將對象實例相關(guān)的類元數(shù)據(jù)輸出
  • 遞歸地輸出類的超類描述直到不再有超類
  • 類元數(shù)據(jù)完了之后网杆,開始從最頂層的超類開始輸出對象實例的實際數(shù)據(jù)值
  • 從上至下遞歸輸出實例的數(shù)據(jù)
3.2 羹饰、實現(xiàn)Parcelable接口

Parcelable是Android SDK API伊滋,其序列化操作完全由底層實現(xiàn),可以在進程內(nèi)队秩、進程間(AIDL)高效傳輸數(shù)據(jù)笑旺。不同版本的API實現(xiàn)方式可能不同,不宜做本地持久化存儲馍资。

3.2.1 序列化舉例
public class P_Shop implements Parcelable {
    public P_Shop(){}
    public String mShopName;
    public int mShopId;
    public String mShopPhone;
    public static int STATIC_VALUE = 100;//靜態(tài)值
    public transient int TRANSIENT_VALUE;//被transient修飾 不能序列化

    /**
     * 從序列化結(jié)構(gòu)中創(chuàng)建原始對象
     */
    protected P_Shop(Parcel in) {
        mShopName = in.readString();
        mShopId = in.readInt();
        mShopPhone = in.readString();
    }

    /**
     * 反序列化
     */
    public static final Creator<P_Shop> CREATOR = new Creator<P_Shop>() {
        /**
         * 從序列化對象中創(chuàng)建原始對象
         */
        @Override
        public P_Shop createFromParcel(Parcel in) {
            return new P_Shop(in);
        }

        /**
         * 創(chuàng)建指定長度的原始對象數(shù)組
         */
        @Override
        public P_Shop[] newArray(int size) {
            return new P_Shop[size];
        }
    };

    /**
     * 序列化:將當前對象寫入序列化結(jié)構(gòu)中
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mShopName);
        dest.writeInt(mShopId);
        dest.writeString(mShopPhone);
    }

    /**
     * 當前對象的內(nèi)容描述燥撞,存在文件描述符時返回1  其余全返回0
     */
    @Override
    public int describeContents() {
        return 0;
    }

    @NonNull
    @Override
    public String toString() {
        return "Parcelable: mShopName is " + mShopName
                + ",mShopId is " + mShopId
                + ",mShopPhone is " + mShopPhone
                + ",STATIC_VALUE is " + STATIC_VALUE
                + ",TRANSIENT_VALUE is " + TRANSIENT_VALUE;
    }
}

執(zhí)行序列化/反序列化:

        //------------------Parcelable------------------
        Context context = this;
        P_Shop shopP = new P_Shop();
        shopP.mShopName = "商品名";
        shopP.mShopId = 2022;
        shopP.mShopPhone = "15700000000";
        shopP.TRANSIENT_VALUE = 1000;

        //序列化過程
        byte[] bytes = PUtil.marshall(shopP);//Parcel->bytes[]
        PUtil.save(context, bytes);//保存bytes[]
        //反序列化過程
        Object object = PUtil.getParcel(context);//bytes[]->Parcel->Object
        if (object == null) return;
        if (object instanceof P_Shop) {
            P_Shop shop = (P_Shop) object;
            Log.e("TTT", shop.toString());
        }

執(zhí)行結(jié)果:

Parcelable: mShopName is 商品名,mShopId is 2022,mShopPhone is 15700000000,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

3.2.2 實現(xiàn)原理

Parcelable 序列化過程中會用到Parcel,Parcel可以被認為是一個包含數(shù)據(jù)或者對象引用的容器迷帜,能夠支持序列化及在跨進程之后的反序列化物舒。Parcelable 的序列化操作在Native層實現(xiàn),通過write內(nèi)存寫入及read讀內(nèi)存數(shù)據(jù)重新生成對象戏锹。Parcelable 將對象進行分解冠胯,且分解后每一部分都是支持可傳遞的數(shù)據(jù)類型。

4锦针、對象序列化的用途Parcelable荠察、Serializable比較

Serializable序列化和反序列化會經(jīng)過大量的I/O操作,產(chǎn)生大量的臨時變量引起GC奈搜;Parcelable是基于內(nèi)存實現(xiàn)的封裝和解封(marshalled& unmarshalled)悉盆,效率比Serializable快很多
下面的測試來自非官方測試,通過Parcelable和Serializable分別執(zhí)行序列化/反序列化過程馋吗,循環(huán)1000次取平均值焕盟,實驗結(jié)果如下:


image.png

數(shù)據(jù)來自 parcelable-vs-serializable,實驗結(jié)果對比Parcelable的效率比Serializable快10倍以上宏粤。

5脚翘、總結(jié)

對比 Serializable Parcelable
所屬API Java API Android SDK API
特點 序列化和反序列化會經(jīng)過大量的I/O操作,產(chǎn)生大量的臨時變量引起GC绍哎,且反序列化時需要反射 基于內(nèi)存拷貝實現(xiàn)的封裝和解封(marshalled& unmarshalled)来农,序列化基于Native層實現(xiàn),不同版本的API實現(xiàn)可能不同
開銷 相對高 相對低
效率 相對低 相對高
適用場景 序列化到本地崇堰、網(wǎng)絡(luò)傳輸 主要內(nèi)存序列化

另外序列化過程中的幾個注意點:

下面兩種成員變量不會參與到默認序列化過程中:
1沃于、static靜態(tài)變量屬于類而不屬于對象
2、transient標記的成員變量
參與序列化的成員變量本身也是需要可序列化的
反序列化時海诲,非可序列化的(如被transient修飾)變量將會調(diào)用自身的無參構(gòu)造函數(shù)重新創(chuàng)建繁莹,因此也要求此成員變量的構(gòu)造函數(shù)必須是可訪問的,否則會報錯饿肺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒋困,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子敬辣,更是在濱河造成了極大的恐慌雪标,老刑警劉巖零院,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異村刨,居然都是意外死亡告抄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門嵌牺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來打洼,“玉大人,你說我怎么就攤上這事逆粹∧即” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵僻弹,是天一觀的道長阿浓。 經(jīng)常有香客問我,道長蹋绽,這世上最難降的妖魔是什么芭毙? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮卸耘,結(jié)果婚禮上退敦,老公的妹妹穿的比我還像新娘。我一直安慰自己蚣抗,他們只是感情好侈百,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忠聚,像睡著了一般设哗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上两蟀,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音震缭,去河邊找鬼赂毯。 笑死,一個胖子當著我的面吹牛拣宰,可吹牛的內(nèi)容都是我干的党涕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼巡社,長吁一口氣:“原來是場噩夢啊……” “哼膛堤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晌该,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肥荔,失蹤者是張志新(化名)和其女友劉穎绿渣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燕耿,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡中符,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了誉帅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淀散。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚜锨,靈堂內(nèi)的尸體忽然破棺而出档插,到底是詐尸還是另有隱情,我是刑警寧澤亚再,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布阀捅,位于F島的核電站,受9級特大地震影響针余,放射性物質(zhì)發(fā)生泄漏饲鄙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一圆雁、第九天 我趴在偏房一處隱蔽的房頂上張望忍级。 院中可真熱鬧,春花似錦伪朽、人聲如沸轴咱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朴肺。三九已至,卻和暖如春坚洽,著一層夾襖步出監(jiān)牢的瞬間戈稿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工讶舰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鞍盗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓跳昼,卻偏偏與公主長得像般甲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹅颊,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容