一、序列化、還原序列化是什嗎?(1) 名詞解釋對象的序列化 : 把Java對象轉換為位元組序列並儲存至一個儲存媒介的過程。
對象的還原序列化:把位元組序列恢複為Java對象的過程。
(2) 序列化詳細解釋對象的序列化涉及三個點關鍵點:Java對象、位元組序列、儲存。
1. Java對象的組成?
Java對象包含變數與方法。但是序列與還原序列化僅處理Java變數而不處理方法,序列與還原序列化僅對資料進行處理。
2. 什麼是字元序列?
字元序列是兩個詞,字元是在電腦和電信領域中,字元(Character)是一個資訊單位。數學上,序列是被排成一列的對象(或事件)。
《字元-維基百科》 , 《序列-維基百科》 說白了就是連續排列的多個字元的集合。類似於1A165613246546
3. 儲存
字元序列需要儲存到一個地方,可以是硬碟也可以是記憶體。
簡單說法是:序列化把當前對象資訊儲存下來。還原序列化剛好相反的操作。
二、Java對象與Java對象序列化的區別?Java對象存在的前提必須在JVM運行期間存在,如果想在JVM非啟動並執行情況下或者在其他機器JVM上擷取指定Java對象,在現有Java對象的機制下都不可能完成。
與Java對象不同的是,如果對Java對象執行序列化操作,因為原理是把Java對象資訊儲存到儲存媒介,所以可以在以上Java對象不可能存在的兩種情況下依然可以使用Java對象。
三、為什麼要使用序列化、還原序列化?根據以上對序列化、還原序列化的理解,這個疑問可以翻譯成,為什麼需要把對象資訊儲存到儲存媒介中並之後讀取出來?
因為二中的解釋,開發中有在JVM非啟動並執行情況下或者在其他機器JVM上擷取指定Java對象的需求。
四、Android 中Serializable與Parcelable區別?兩種都是用於支援序列化、還原序列化話操作,兩者最大的區別在於儲存媒介的不同,Serializable使用IO讀寫儲存在硬碟上,而Parcelable是直接在記憶體中讀寫,很明顯記憶體的讀寫速度通常大於IO讀寫,所以在Android中通常優先選擇Parcelable。
Serializable不是當前關注的焦點,不過可以查看《Java序列化演算法透析》這篇文章中實現一個簡單的Serializable例子,查看序列化產生的IO檔案,並且以16進位讀取並一一解釋每一個16進位數位含義。
五、Parcelable舉例在Android中實現Parcelable介面的類可以支援序列與還原序列化,以下是一個實現的舉例:
1. 實現Parcelable介面
2. 添加實體屬性
3. 覆寫writeToParcel(Parcel dest, int flags)方法,指定寫入Parcel類的資料。
4. 建立Parcelable.Creator靜態對象,有兩個方法createFromParcel(Parcel in)與newArray(int size),前者指定如何從Parcel中讀取出資料對象,後者建立一個數組。
5. 覆寫describeContents方法,預設返回0。
public class Gril implements Parcelable { private int mAge; // 年齡 private boolean mSexy; // 是否性感 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mAge); dest.writeByte((byte) (mSexy ? 1 : 0)); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Gril createFromParcel(Parcel in) { Gril gril = new Gril(); gril.mAge = in.readInt(); gril.mSexy = in.readByte() != 0; return gril; } public Gril[] newArray(int size) { return new Gril[size]; } }; @Override public int describeContents() { return 0; }}
六、Parcelable原理
從上面的例子中可以看出,具體的寫入(dest.writeInt(mAge);)與讀取(gril.mAge = in.readInt();)都是針對Parcel對象進行的操作,下面貼出的是Parcle 讀寫int類型資料的定義。
public final class Parcel { ...... /** * Write an integer value into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ public final native void writeInt(int val); /** * Read an integer value from the parcel at the current dataPosition(). */ public final native int readInt(); ......}
從上面代碼可以看出都是native方法說明都是使用JNI,其具體位置在system/frameworks/base/core/jni/android_util_Binder.cpp ,以下也僅以int類型讀寫為例
static void android_os_Parcel_writeInt(JNIEnv* env, jobject clazz, jint val){ Parcel* parcel = parcelForJavaObject(env, clazz); if (parcel != NULL) { const status_t err = parcel->writeInt32(val); if (err != NO_ERROR) { jniThrowException(env, java/lang/OutOfMemoryError, NULL); } }}static jint android_os_Parcel_readInt(JNIEnv* env, jobject clazz){ Parcel* parcel = parcelForJavaObject(env, clazz); if (parcel != NULL) { return parcel->readInt32(); } return 0;}
從上面可以看出都會調用Parcel實現且分別調用writeInt32與readInt32函數,接著來看看具體實現。位置:/system/frameworks/base/libs/binder/Parcel.cpp
status_t Parcel::writeInt32(int32_t val){ return writeAligned(val);}templatestatus_t Parcel::writeAligned(T val) { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(val)) <= mDataCapacity) {restart_write: *reinterpret_cast(mData+mDataPos) = val; return finishWrite(sizeof(val)); } status_t err = growData(sizeof(val)); if (err == NO_ERROR) goto restart_write; return err;}status_t Parcel::readInt32(int32_t *pArg) const{ return readAligned(pArg);}templatestatus_t Parcel::readAligned(T *pArg) const { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(T)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += sizeof(T); *pArg = *reinterpret_cast(data); return NO_ERROR; } else { return NOT_ENOUGH_DATA; }}
以下4點摘自《探索Android中的Parcel機制(上)》
有興趣的朋友可以自己讀一下,不難理解,這裡把基本的思路總結一下:
1. 整個讀寫全是在記憶體中進行,主要是通過malloc()、realloc()、memcpy()等記憶體操作進行,所以效率比JAVA序列化中使用外部儲存空間會高很多;
2. 讀寫時是4位元組對齊的,可以看到#define PAD_SIZE(s) (((s)+3)&~3)這句宏定義就是在做這件事情;
3. 如果預分配的空間不夠時newSize = ((mDataSize+len)*3)/2;會一次多分配50%;
4. 對於普通資料,使用的是mData記憶體位址,對於IBinder類型的資料以及FileDescriptor使用的是mObjects記憶體位址。後者是通過flatten_binder()和unflatten_binder()實現的,目的是還原序列化時讀出的對象就是原對象而不用重新new一個新對象。
七、序列化還原序列化Parcelable實驗?1. 任何實體類都需要複寫Parcelable介面嗎?
2. 如果子類新增屬性,需要複寫父類writeToParcel與CREATOR嗎?
3. writeToParcel 與 createFromParcel 對變數的讀寫前後順序可以不一致嗎,會出現什麼結果?
4. 讀寫Parcelable對象(寫操作dest.writeParcelable(obj, flags); 讀操作in.readParcelable(ObjectA.class.getClassLoader()); )
5. 讀寫Parcelable對象數組
dest.writeParcelableArray(mClassNameList.toArray(new ClassName[mClassNameList.size()]), flags);Parcelable[] parcelableArr = in.readParcelableArray(ClassName.class.getClassLoader());ArrayList arrayList = new ArrayList();for (Parcelable object : parcelableArr) { arrayList.add((ClassName)object);}
八、自己實現序列與還原序列化機制《C 語言的資料序列化 (C語言實現序列化機制的思路)》