文章目錄
什麼是序列化?爲什麼要序列化?怎麼進行序列化?
序列化定義:將一個類或對象轉換成可存儲、可傳輸狀態的過程。對象序列化後,可以在進程內/進程間、網絡間進行傳輸,也可以做本地持久化存儲。
爲什麼要序列化: 系統底層並不認識對象,數據傳輸是以字節序列形式傳遞,以進程間通信爲例,需要將對象轉化爲字節序列(字節序列中包括該對象的類型,成員信息等),然後在目標進程裏通過反序列化字節序列,將字節序列轉換成對象。
序列化方式:
- Serializable(Java提供 後面簡稱爲S)
- Parcelable(Android特有 下面簡稱爲P)
注意事項
- 參與序列化的成員變量本身也是需要可序列化的,未實現序列化的內部變量不會參與序列化/反序列化
- 未序列化的變量將會調用自身的無參構造函數重新創建
- 下面兩種成員變量不會參與到序列化過程中:
1、static靜態變量屬於類而不屬於對象
2、transient標記的成員變量
Serializable
S是Java API,是一個通用的序列化機制,可以將對象序列化並保存在本地或內存中。S是一個空接口:
public interface Serializable {}
S用來標識當前類可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化。
實現原理
兩個相關概念:
ObjectStreamClass: 序列化類的描述符。它包含類的名稱和serialVersionUID。它由Java VM加載,可以使用lookup方法找到或創建。
ObjectStreamField: 類(可序列化)的可序列化字段的描述。ObjectStreamFields數組用於聲明類的可序列化字段。
操作S序列化過程的有ObjectOutputStream/ObjectInputStream,他們都是在Java層實現的。
1、序列化過程(writeObject方法)
-
通過ObjectStreamClass記錄目標對象的類型、類名等信息,內部有個ObjectStreamFields數組,用來記錄目標對象的內部變量(內部變量可以是基本類型,也可以是自定義類型,但是必須都支持序列化—必須是S不能是P)。
-
首先通過ObjectStreamClass.lookup()找到或創建ObjectStreamClass,然後調用defaultWriteFields方法,在方法中通過getPrimFieldValues()獲取基本數據類型並賦值到primVals(byte[]類型)中,再通過getObjFieldValues()獲取到自定義對象(通過Unsafe類實現而不是反射)並賦值到objVals(Object[]類型)中,接着遍歷objVals數組,然後遞歸調用writeObject方法重複上述操作。
2、反序列化過程(readObject方法)
- 通過readClassDescriptor()讀取InputStream裏的數據並初始化ObjectStreamClass類,再根據這個實例通過反射創建目標對象實例。
serialVersionUID
serialVersionUID用來輔助序列化和反序列化的過程的,序列化過程中會把類中的serialVersionUID寫入序列化文件中,在反序列化時,檢測序列化文件中serialVersionUID和當前類中的serialVersionUID是否一致,如果一致,纔可以繼續進行反序列化操作;如果不一致,說明類序列化後發生了一些改變,比如成員變量的類型發生改變,此時是不能反序列化的。
是否需要指定serialVersionUID? 答案是肯定的,如果不指定serialVersionUID,在序列化時系統也會自動幫我們生成一個並寫入序列化文件中,此時如果當前類發生了微小的變化也會導致serialVersionUID改變,從而反序列化失敗;如果指定了serialVersionUID,即使類發生某些修改也不會影響反序列化,但一些類結構發生改變,如類名改變、成員變量的類型發生了改變,此時即使serialVersionUID驗證通過了,反序列化依然會失敗。
舉例
Shop_S.java:
public class Shop_S extends MyBean implements Serializable {
private static final long serialVersionUID = -1399695071515887643L;
public String mShopName;
public int mShopId;
public String mShopPhone;
public static int STATIC_VALUE = 100;//靜態值
public transient int TRANSIENT_VALUE = 100;//被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;
}
}
執行序列化和反序列化過程:
public static void main(String[] args) throws IOException {
//------------------Serializable------------------
Shop_S shop = new Shop_S();
shop.mShopName = "便利蜂";
shop.mShopId = 2020;
shop.mShopPhone = "18888888888";
shop.TRANSIENT_VALUE = 1000;
//序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("shop.obj"));
outputStream.writeObject(shop);
outputStream.close();
//反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("shop.obj"));
try {
Shop_S shop_s = (Shop_S) inputStream.readObject();
System.out.println(shop_s.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
inputStream.close();
}
}
執行結果:
Serializable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0
結果看到反序列化成功,從序列化結構中又重新生成了對象,這裏注意一點,類中的變量TRANSIENT_VALUE是由transient修飾的,不能被序列化,所以反序列化時得到的是默認值。
Parcelable
P是Android SDK API,通過IBinder作爲消息載體,可以在進程內、進程間(AIDL)高效傳輸數據,其序列化操作完全由底層實現,不同版本的API實現方式可能不同,不宜做本地持久化存儲。
實現原理
P的序列化操作在Native層實現,通過write內存寫入及read讀內存數據重新生成對象。P將對象進行分解,且分解後每一部分都是支持可傳遞的數據類型。
舉例
1、實現P接口,複寫describeContents方法和writeToParcel方法
2、實例化靜態內部對象CREATOR,實現接口Parcelable.Creator 。
Shop_P.java:
public class Shop_P implements Parcelable {
public String mShopName;
public int mShopId;
public String mShopAddress;
/**
* 從序列化結構中創建原始對象
*
* @param in
*/
protected Shop_P(Parcel in) {
mShopName = in.readString();
mShopId = in.readInt();
mShopAddress = in.readString();
}
//反序列化
public static final Creator<Shop_P> CREATOR = new Creator<Shop_P>() {
//根據字節序列重新生成對象
@Override
public Shop_P createFromParcel(Parcel in) {
return new Shop_P(in);
}
//供反序列化本類數組時調用
@Override
public Shop_P[] newArray(int size) {
return new Shop_P[size];
}
};
//內容接口描述,當前對象中存在文件描述符時返回1,其他都返回0即可
@Override
public int describeContents() {
return 0;
}
/**
* 將當前對象寫入序列化結構中
*
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mShopName);
dest.writeInt(mShopId);
dest.writeString(mShopAddress);
}
}
注意:createFromParcel()和writeToParcel()方法中對應變量讀寫的順序必須是一致的,否則序列化會失敗並拋異常。
Parcel處理工具:
public class PUtil {
//marshall Parcel將自身保存的數據以byte數組形式返回
public static byte[] marshall(Parcelable parcelable) {
Parcel parcel = Parcel.obtain();
parcel.setDataPosition(0);
parcelable.writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.recycle();
return bytes;
}
//從byte數組中獲取數據,存入自身的Parcel中
public static Parcel unmarshall(byte[] bytes) {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
return parcel;
}
private static final String SP_NAME = "sp_parcel";
private static final String PARCEL_KEY = "parcel_key";
//將bytes經過base64轉換成字符串並存儲到sp種種
public static void save(Context context, byte[] bytes) {
SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
String saveStr = Base64.encodeToString(bytes, 0);
editor.putString(PARCEL_KEY, saveStr);
editor.apply();
}
//從sp中取出字符串並轉換成bytes 最終轉換成Parcel
public static Parcel getParcel(Context context) {
SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
byte[] bytes = Base64.decode(preferences
.getString(PARCEL_KEY, ""), Base64.DEFAULT);
return unmarshall(bytes);
}
}
Shop_P shopP = new Shop_P();
shopP.mShopName = "便利蜂";
shopP.mShopId = 2020;
shopP.mShopPhone = "18888888888";
shopP.TRANSIENT_VALUE = 1000;
byte[] bytes = PUtil.marshall(shopP);//Parcel->bytes[]
PUtil.save(context, bytes);//保存bytes[]
Parcel parcel = PUtil.getParcel(context);//bytes[]->Parcel
Shop_P shop_p = Shop_P.CREATOR.createFromParcel(parcel);
Log.e("TTT", shop_p.toString());
執行結果:
Parcelable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0
Parcelable、Serializable比較
效率對比
S序列化和反序列化會經過大量的I/O操作,產生大量的臨時變量引起GC;P是基於內存實現的封裝和解封(marshalled& unmarshalled),效率比S快很多。
容錯率對比
字段改變對反序列化的影響
改變字段 | Serializable | Parcelable |
---|---|---|
增加字段 | ✔️ | 追加到末尾:✔️ 其他:❌ |
刪除字段 | ✔️ | ❌ |
修改字段類型 | ❌ | ❌ |
總結
對比 | Serializable | Parcelable |
---|---|---|
所屬API | Java API | Android SDK API |
特點 | 序列化和反序列化會經過大量的I/O操作,產生大量的臨時變量引起GC,且反序列化時需要反射 | 基於內存拷貝實現的封裝和解封(marshalled& unmarshalled),序列化基於Native層實現,不同版本的API實現可能不同 |
開銷 | 相對高 | 相對低 |
效率 | 相對低 | 相對高 |
適用場景 | 序列化到本地、網絡傳輸 | 內存序列化 |
參考
【1】https://www.jianshu.com/p/1b362e374354
【2】https://www.wanandroid.com/wenda/show/9002
【3】https://wangchunlei000.github.io/bigthunder.github.io/2019/07/19/Android%E4%B8%AD%E4%B8%A4%E7%A7%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%96%B9%E5%BC%8F%E7%9A%84%E6%AF%94%E8%BE%83Serializable%E5%92%8CParcelable/
【4】http://darryrzhong.xyz/2019/09/15/Android%E4%B9%8B%E5%BA%8F%E5%88%97%E5%8C%96%E8%AF%A6%E8%A7%A3/
【5】https://mp.weixin.qq.com/s/Qj-Bed2G6ekQ4DmogUffWw