是什么蝗岖?為啥用诲宇?怎么用?——靈魂三連問
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 特殊變量序列化
- 靜態(tài)變量的序列化
序列化并不保存靜態(tài)變量承边,序列化保存的是對象的狀態(tài),而靜態(tài)變量是類的狀態(tài)石挂。 - Transient關(guān)鍵字
transient
關(guān)鍵字的作用就是控制變量的序列化博助,在變量聲明前加上該關(guān)鍵字,可以阻止該變量序列化到文件中痹愚,在反序列化后富岳,transient
變量會被設(shè)為初始值,如int型的為0里伯,對象型的為null城瞎。 - 父類的序列化特性
如果子類實現(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é)果如下:
數(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ù)必須是可訪問的,否則會報錯饿肺。