今天再來談談Android中的物件序列化,你了解多少呢?
序列化指的是什么?有什么用
序列化指的是講物件變成有序的位元組流,變成位元組流之后才能進行傳輸存盤等一系列操作,
反序列化就是序列化的相反操作,也就是把序列化生成的位元組流轉為我們記憶體的物件,
介紹下Android中兩種序列化介面
Serializable
是Java提供的一個序列化介面,是一個空介面,專門為物件提供序列化和反序列化操作,具體使用如下:
public class User implements Serializable {
private static final long serialVersionUID=519067123721561165l;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
實作Serializable介面,宣告一個serialVersionUID,
到這里可能有人就問了,不對啊,平時沒有這個serialVersionUID啊,沒錯,serialVersionUID不是必須的,因為不寫的話,系統會自動生成這個變數,它有什么用呢?當序列化的時候,系統會把當前類的serialVersionUID寫入序列化的檔案中,當反序列化的時候會去檢測這個serialVersionUID,看他是否和當前類的serialVersionUID一致,一樣則可以正常反序列化,如果不一樣就會報錯了,
所以這個serialVersionUID就是序列化和反序列化程序中的一個標識,代表一致性,不加的話會有什么影響?如果我們序列化后,改動了這個類的某些成員變數,那么serialVersionUID就會改變,這時候再拿之前序列化的資料來反序列化就會報錯,所以如果我們手動指定serialVersionUID就能保證最大限度來恢復資料,
Serializable的實質其實是是把Java物件序列化為二進制檔案,然后就能在行程之間傳遞,并且用于網路傳輸或者本地存盤等一系列操作,因為他的本質就存盤了檔案,可以看看原始碼:
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
desc = ObjectStreamClass.lookup(cl, true);
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
}
...
}
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
...
try {
desc.checkSerialize();
//寫入二進制檔案,普通物件開頭的魔數0x73
bout.writeByte(TC_OBJECT);
//寫入對應的類的描述符,見底下原始碼
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
public long getSerialVersionUID() {
// 如果沒有定義serialVersionUID,序列化機制就會呼叫一個函式根據類內部的屬性等計算出一個hash值
if (suid == null) {
suid = AccessController.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
return computeDefaultSUID(cl);
}
}
);
}
return suid.longValue();
}
可以看到是通過反射獲取物件以及物件屬性的相關資訊,然后將資料寫到了一個二進制檔案,并且寫入了序列化協議版本等等,
而獲取·serialVersionUID·的邏輯也體現出來,如果id為空則會生成計算一個hash值,
Parcelable
Android自帶的介面,使用起來要麻煩很多:需要實作Parcelable介面,重寫describeContents(),writeToParcel(Parcel dest, @WriteFlags int flags),并添加一個靜態成員變數CREATOR并實作Parcelable.Creator介面
public class User implements Parcelable {
private int id;
protected User(Parcel in) {
id = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
createFromParcel,User(Parcel in) ,代表從序列化的物件中創建原始物件newArray,代表創建指定長度的原始物件陣列writeToParcel,代表將當前物件寫入到序列化結構中,describeContents,代表回傳當前物件的內容描述,如果還有檔案描述符,回傳1,否則回傳0,
Parcelable的存盤是通過Parcel存盤到記憶體的,簡單地說,Parcel提供了一套機制,可以將序列化之后的資料寫入到一個共享記憶體中,其他行程通過Parcel可以從這塊共享記憶體中讀出位元組流,并反序列化成物件,
這其中實際又是通過native方法實作的,具體邏輯我就沒有去分析了,如果有大神朋友可以在評論區決議下,
當然,Parcelable也是可以持久化的,涉及到Parcel中的unmarshall和marshall方法, 這里簡單貼一下代碼:
protected void saveParce() {
FileOutputStream fos;
try {
fos = getApplicationContext().openFileOutput(TAG,
Context.MODE_PRIVATE);
BufferedOutputStream bos = new BufferedOutputStream(fos);
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(new ParceData(), 0);
bos.write(parcel.marshall());
bos.flush();
bos.close();
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
protected void loadParce() {
FileInputStream fis;
try {
fis = getApplicationContext().openFileInput(TAG);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
ParceData data = https://www.cnblogs.com/jimuzz/p/parcel.readParcelable(ParceData.class.getClassLoader());
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
總結
0)兩者的區別,我們該怎么選擇?
Serializable是Java提供的序列化介面,使用簡單但是開銷很大,序列化和反序列化程序都需要大量I/O操作,
Parcelable是Android中提供的,也是Android中推薦的序列化方式,雖然使用麻煩,但是效率很高,
所以,如果是記憶體序列化層面,那么還是建議Parcelable,因為他效率會比較高,
如果是網路傳輸和存盤磁盤情況,就推薦Serializable,因為序列化方式比較簡單,而且Parcelable不能保證,當外部條件發生變化時資料的連續性,
1)對于記憶體序列化方面建議用Parcelable,為什么呢?
- 因為
Serializable是存盤了一個二進制檔案,所以會有頻繁的IO操作,消耗也比較大,而且用到了大量反射,反射操作也是耗時的,相比之下Parcelable就要效率高很多,
2)對于資料持久化還是建議用Serializable,為什么呢?
- 首先,
Serializable本身就是存盤到二進制檔案,所以用于持久化比較方便,而Parcelable序列化是在記憶體中操作,如果行程關倍訓者重啟的時候,記憶體中的資料就會消失,那么Parcelable序列化用來持久化就有可能會失敗,也就是資料不會連續完整,而且Parcelable還有一個問題是兼容性,每個Android版本可能內部實作都不一樣,知識用于記憶體中也就是傳遞資料的話是不影響的,但是如果持久化可能就會有問題了,低版本的資料拿到高版本可能會出現兼容性問題, 所以還是建議用Serializable進行持久化,
3)Parcelable一定比Serializable快嗎?
- 有個比較有趣的例子是:當序列化一個超級大的物件圖表(表示通過一個物件,擁有通過某路徑能訪問到其他很多的物件),并且每個物件有10個以上屬性時,并且
Serializable實作了writeObject()以及readObject(),在平均每臺安卓設備上,Serializable序列化速度大于Parcelable3.6倍,反序列化速度大于1.6倍.
具體原因就是因為Serilazable的實作方式中,是有快取的概念的,當一個物件被決議過后,將會快取在HandleTable中,當下一次決議到同一種型別的物件后,便可以向二進制流中,寫入對應的快取索引即可,但是對于Parcel來說,沒有這種概念,每一次的序列化都是獨立的,每一個物件,都當作一種新的物件以及新的型別的方式來處理,
參考
Parcelable
拜拜
有一起學習的小伙伴可以關注下??我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/211800.html
標籤:Android
上一篇:Java8 Stream:2萬字20個實體,玩轉集合的篩選、歸約、分組、聚合
下一篇:Android序列化問題與思考

