Android 存儲優(yōu)化系列專題
- SharedPreferences 系列
《Android 之不要濫用 SharedPreferences》
《Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失》
- ContentProvider 系列(待更)
《Android 存儲選項之 ContentProvider 啟動存在的暗坑》
《Android 存儲選項之 ContentProvider 深入分析》
- 對象序列化系列
《Android 對象序列化之你不知道的 Serializable》
《Android 對象序列化之 Parcelable 取代 Serializable ?》
《Android 對象序列化之追求性能完美的 Serial》
- 數(shù)據(jù)序列化系列(待更)
《Android 數(shù)據(jù)序列化之 JSON》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 使用》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 源碼分析》
- SQLite 存儲系列
《Android 存儲選項之 SQLiteDatabase 創(chuàng)建過程源碼分析》
《Android 存儲選項之 SQLiteDatabase 源碼分析》
《數(shù)據(jù)庫連接池 SQLiteConnectionPool 源碼分析》
《SQLiteDatabase 啟用事務源碼分析》
《SQLite 數(shù)據(jù)庫 WAL 模式工作原理簡介》
《SQLite 數(shù)據(jù)庫鎖機制與事務簡介》
《SQLite 數(shù)據(jù)庫優(yōu)化那些事兒》
前言
對象序列化系列,主要內容包括:Java 原生提供的 Serializable 渺贤,更加適合 Android 平臺輕量且高效的 Parcelable雏胃,以及追求性能完美的 Serial。該系列內容主要結合源碼的角度分析它們各自的優(yōu)缺點以及合適的使用場景志鞍。
今天先來聊聊 Java 原生提供的 Serializable 對象序列化機制瞭亮,本文不是與大家一起探討關于 Serializable 的具體使用,相信關于 Serializable 大家肯定也不會感到陌生固棚,Serializable 是 Java 原生的序列化機制统翩,在 Android 中也有被廣泛使用仙蚜。我們可以通過 Serializable 將對象持久化存儲,也可以通過 Bundle 傳遞 Serializable 的序列化數(shù)據(jù)厂汗。
Serializable 簡單實現(xiàn)背后包含著復雜的計算邏輯委粉,本文也將結合源碼角度深入分析 Serializable 背后實現(xiàn)原理。
序列化/反序列化
- 什么是對象序列化
大家是否有思考過娶桦,什么是序列化贾节?應用程序中的對象存儲在內存中,如果此時我們想把對象存儲下來或者在網絡上傳輸衷畦,這個時候就需要用到對象的序列化和反序列化栗涂。
對象序列化就是把一個 Object 對象的所有信息表示成一個字節(jié)序列,這包括 Class 信息祈争、繼承關系斤程、訪問權限、變量類型以及數(shù)值信息等菩混。
數(shù)據(jù)存儲不一定就是將數(shù)據(jù)存放到磁盤中忿墅,比如放到內存中、通過網絡傳輸也可以算是存儲的一種形式墨吓∏蜇埃或者我們也可以把這個過程叫做對象或者數(shù)據(jù)的序列化。
Serializable 簡單使用
“好像我們只需要簡單實現(xiàn) Serializable 接口帖烘,它就可以完成序列化/反序列化工作了”亮曹, 那它是如何工作的呢?實際上 Serializalbe 這種簡單機制是通過犧牲掉執(zhí)行性能為代價換來的秘症,也就是在時間開銷和使用成本的權衡上照卦,Serializable 機制選擇是使用成本優(yōu)先。
public final class Person implements Serializable {
private String name;
private int age;
private String like;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLike() {
return like;
}
public void setLike(String like) {
this.like = like;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", like='" + like + '\'' +
'}';
}
}
使用 ObjectOutputStream 將 Person 對象序列化到磁盤:
public void testWritePersonObject() {
Person person;
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(openFileOutput("person", Context.MODE_PRIVATE));
person = new Person();
out.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtil.close(out);
}
}
整個使用過程是不是非常簡單乡摹?雖然 Serializable 使用真的非常簡單役耕,但是也有一些需要注意的事項。另外它還有一些進階的使用技巧聪廉。文中也會詳細介紹到瞬痘,并且最后也會給大家總結出該部分內容。
Serializable 背后實現(xiàn)原理
- ObjectOutputStream 和 ObjectInputStream
不知道大家是否跟我一樣板熊,在最初使用 Serializable 時是否會有這樣的疑問:“我們只需要實現(xiàn)這個接口框全,好像什么都不用管了,那它是如何實現(xiàn)序列化的呢干签?”
實際上 Serializable 的實現(xiàn)原理分別在 ObjectOutputStream 和 ObjectInputStream 中(本文主要給大家介紹序列化過程)津辩,整個序列化過程使用了大量反射和臨時變量,而且在序列化對象的時候,不僅會序列化當前對象本身喘沿,還需要遞歸序列化對應引用的其他對象闸度。
先來看下 ObjectObjectStream 的構造方法:
public ObjectOutputStream(OutputStream out) throws IOException {
//檢查繼承權限
verifySubclass();
//構造一個BlockDataOutputStream用于向out寫入序列化數(shù)據(jù)
bout = new BlockDataOutputStream(out);
//構造一個大小為10,負載因子為3的HandleTable和ReplaceTable
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
//有參構造方法默認為false,
enableOverride = false;
writeStreamHeader();
//將緩存模式打開蚜印,寫入數(shù)據(jù)時先寫入緩沖區(qū)
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
我們從上面例子中 ObjectOutputStream writeObject 方法開始說起:
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
//這里判斷是否需要重寫writeObject()
//如果使用無參構造方法創(chuàng)建莺禁,該變量默認為true,此時必須重寫writeObject()
//此時實際需要重寫的方法為:writeObjectOverride晒哄,否則拋出異常
writeObjectOverride(obj);
return;
}
try {
//執(zhí)行writeObject0()
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
try {
writeFatalException(ex);
} catch (IOException ex2) {}
}
throw ex;
}
}
在 writeObject 方法中首先判斷當前是否重寫了 writeObject 方法睁宰,如果我們使用無參構造方法創(chuàng)建 ObjectOutStream 實例,此時必須重寫該方法(實際重寫的是 writeObjectOverride 方法)柒傻,否則會拋出異常。
接著跟蹤 writeObject0 方法:
private void writeObject0(Object obj, boolean unshared) throws IOException {
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
int h;
//處理以前寫過和不可替換的對象
//如果obj為null,此時一定返回null
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
}
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
Class repCl;
//創(chuàng)建ObjectStreamClass實例
//ObjectStreamClass表示序列化對象的class詳細信息
desc = ObjectStreamClass.lookup(cl, true);
if (desc.hasWriteReplaceMethod()
&& (obj = desc.invokeWriteReplace(obj)) != null
&& (repCl = obj.getClass()) != cl) {
//如果實現(xiàn)了writeReplace方法替換掉原寫入對象 與
//該方法返回對象不為null 與
//返回對象類型不是當前序列化類型
cl = repCl;
//生成替換對象類型的ObjectStreamClass
desc = ObjectStreamClass.lookup(cl, true);
}
if (enableReplace) {
//如果通過enableReplaceObject()方法設置enablReplace
//此時回調replaceObject较木,需要子類重寫红符,否則返回與上述obj一致。
//這里是繼承ObjectOutputStream之后重寫了replaceObject方法
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
//重新賦值要序列化的對象
obj = rep;
}
if (obj != orig) {
//如果替換了原序列化對象
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
}
}
// remaining cases
// BEGIN Android-changed
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed
} 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) {
//我們只看實現(xiàn) Serializable 序列化過程
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
該方法中我們重點關心 desc = ObjectStreamClass.lookup 和
writeOrdinaryObject 兩個方法伐债。
首先看 ObjectStreamClass.lookup 方法:它返回 ObjectStreamClass 實例预侯,ObjectStreamClass 保存了要序列化對象的 Class 相關信息:Class 名稱、serialVersionUID峰锁、實現(xiàn)了 Serializable 還是 Externalizable 接口萎馅,是否自行實現(xiàn)了 writeObject 和 readObject 方法等內容。每個序列化對象類型對應一個 ObjectStreamClass 實例虹蒋。
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
if (!(all || Serializable.class.isAssignableFrom(cl))) {
//all在ObjectOutStream中默認傳遞true
//判斷對象類型是否實現(xiàn)了Serializable接口
//如果all=false 與 未實現(xiàn)Serializable接口則直接return糜芳。
return null;
}
//清除失去Class引用的對應ObjectStreamClass實例緩存
//不過這在Android好像并不能生效,因為在Android JVM 中類是不可以被卸載的
processQueue(Caches.localDescsQueue, Caches.localDescs);
//將寫入對象類交給弱引用持有魄衅,WeakClassKey繼承自WeakReference
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
//根據(jù)類對象獲取是否存在緩存的EntryFutrue或ObjectStreamClass
//這里可能大家會有疑問郭厌,每次new WeakClassKey() 作為key摇天?實際上它內部使用
//Class作為hash值的計算
Reference<?> ref = Caches.localDescs.get(key);
Object entry = null;
if (ref != null) {
//如果引用不為null,獲取引用的實際類型
//在Android中只要是之前緩存過聘萨,則該ref永遠不為null(Class 不可以被卸載)
//獲取該ref引用的對象
entry = ref.get();
}
EntryFuture future = null;
//如果引用的對象已經被回收
if (entry == null) {
//創(chuàng)建EntryFuture华弓,并使用軟引用關聯(lián)族跛。
EntryFuture newEntry = new EntryFuture();
Reference<?> newRef = new SoftReference<>(newEntry);
do {
if (ref != null) {
//此時實際的引用對象已經被回收了鞍爱,故ref引用就沒有用了
//從緩存中刪除該鍵值對
Caches.localDescs.remove(key, ref);
}
//localDescs是ConcurrentHashMap,
//putIfAbsent()方法初肉,不存在該key就添加進去,返回null
//如果存在就返回之前的Reference荆责。
ref = Caches.localDescs.putIfAbsent(key, newRef);
if (ref != null) {
entry = ref.get();
}
//這里是要保證該key(Class)對應的Reference中持有的對象沒有被回收
} while (ref != null && entry == null);
if (entry == null) {
//如果entry為null喻粹,此時future就是新創(chuàng)建的EntryFuture
future = newEntry;
}
}
if (entry instanceof ObjectStreamClass) {
//如果拿到的是ObjectStreamClass,直接返回
return (ObjectStreamClass) entry;
}
if (entry instanceof EntryFuture) {
//如果拿到的是EntryFuture
future = (EntryFuture) entry;
if (future.getOwner() == Thread.currentThread()) {
//如果創(chuàng)建這個EntryFuture線程就是當前線程
//意思就是當前新創(chuàng)建的這個EntryFuture
//這里實際判斷該future是否是剛創(chuàng)建的newEntry
entry = null;
} else {
//否則就獲取之前的set的
entry = future.get();
}
}
if (entry == null) {
try {
//如果是新創(chuàng)建的future草巡,此時就創(chuàng)建新的ObjectStreamClass
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
if (future.set(entry)) {
//EntryFuture中沒有關聯(lián)過entry
//關聯(lián),并緩存為當前key對應引用關系
Caches.localDescs.put(key, new SoftReference<Object>(entry));
} else {
// nested lookup call already set future
entry = future.get();
}
}
//entr必須為是ObjectStreamClass,否則拋異常
if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
} else if (entry instanceof RuntimeException) {
throw (RuntimeException) entry;
} else if (entry instanceof Error) {
throw (Error) entry;
} else {
throw new InternalError("unexpected entry: " + entry);
}
}
lookup() 方法的第一個 if 語句就判斷了要序列化的類是否實現(xiàn)了 Serializable 接口山憨,否則返回 null查乒,此時回到 ObjectOutputStream 的 writeObject0() 方法將會出現(xiàn)異常,這里也證明要序列化對象必須實現(xiàn) Serializable 接口郁竟。
lookup 整個方法看起來有些繁瑣玛迄,實際是從緩沖中獲取該序列化類型對應的 ObjectStreamClass 對象,ObjectStreamClass 的引入一方面是為了代碼解耦棚亩,另一個重要方面是提高獲取類信息的速度(ObjectStreamClass 保存了當前序列化類的相關信息)蓖议,系統(tǒng)通過 SoftReference 持有 Class 和 ObjectStreamClass 的映射關系。如果反復對一個類的實例進行序列化操作讥蟆,能夠有效減少耗時勒虾,提高緩存命中率。
源碼中有幾個需要說明的地方:
WeakClassKey 繼承自 WeakReference瘸彤,這里持有要序列對象的 Class修然,不過正如在注釋中所述:這在 Android 中好像并不能生效,因為在 Android 平臺 Java 虛擬機中质况,類對象是不可以被回收的(這也是出于性能的考慮)愕宋。
localDescs 是一個 ConcurrentHashMap 對象,源碼中可以看到每次 new WeakClassKey() 對象作為 key 獲取是否已經緩存過對應的 ObjectStreamClass 實例结榄?實際上 WeakClassKey 重寫了 hashCode() 方法中贝,故這里采用的是 Class 對象來計算的 Hash 值。
源碼中的 do {} while() 作用是保證該 Class 對象能夠正常獲取到對應的 ObjectStreamClass 對象臼朗。
- EntryFuture 是 ObjectStreamClass 的內部類邻寿,引入它的作用我個人認為是多線程調用 lookup() 方法而設立的。主要體現(xiàn)在判斷:if(future.getOwner() == Thread.currentThread()) 如果成立說明此時 EntryFuture 就是剛剛創(chuàng)建的依溯。否則是從緩存中獲取到的(但不代表就是其它線程創(chuàng)建的)老厌。
跟蹤到這里有必要進一步分析下 ObjectStreamClass 這個類,看下它的構造方法:
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
//序列化對象的類名稱
name = cl.getName();
//判斷是否是代理類
isProxy = Proxy.isProxyClass(cl);
//是否是Enum
isEnum = Enum.class.isAssignableFrom(cl);
//是否實現(xiàn)了Serialable接口
serializable = Serializable.class.isAssignableFrom(cl);
//是否實現(xiàn)了Externalizable接口
externalizable = Externalizable.class.isAssignableFrom(cl);
//獲取其父類
Class<?> superCl = cl.getSuperclass();
//與之前分析的lookup()方法一致黎炉,這里會遞歸調用每個父類枝秤,
//因為lookup()又會創(chuàng)建對應類的ObjectStramClass
//superDesc表示其父類的ObjectStreamClass
superDesc = (superCl != null) ? lookup(superCl, false) : null;
//表示當前的ObjectStreamClass
localDesc = this;
if (serializable) {
//如果實現(xiàn)了Serializable接口
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
//枚舉類型,此時沒有 Field
return null;
}
if (cl.isArray()) {
//數(shù)組類型
fields = NO_FIELDS;
return null;
}
//生成serialVersionUID
//會去反射我們有沒有聲明serialVersionUID
suid = getDeclaredSUID(cl);
try {
//反射獲取該類所有需要被序列化的Field
//首先反射是否定義了serialPersistentFields慷嗜,如果存在則根據(jù)其內部聲明為準淀弹。
//否則直接反射獲取該類所有的字段(過濾static 和 transient)
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
//如果實現(xiàn)了Enternalizable接口,獲取其構造方法
cons = getExternalizableConstructor(cl);
} else {
//獲取構造方法
cons = getSerializableConstructor(cl);
//反射獲取是否實現(xiàn)了writeObject方法
writeObjectMethod = getPrivateMethod(cl, "writeObject",
//參數(shù)類是 ObjectOutputStream
new Class<?>[]{ObjectOutputStream.class},
Void.TYPE);
//反射獲取是否實現(xiàn)了readObject方法
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[]{ObjectInputStream.class},
Void.TYPE);
//反射獲取是否實現(xiàn)了readObjectNoData方法
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
//反射獲取是否實現(xiàn)了writeReplace
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
//反射獲取是否實現(xiàn)了readResolve
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
// ... 省略
}
ObjectStreamClass 類的構造方法做的事情還是蠻多的:獲取序列化類的相關信息庆械、一系列類型檢查薇溃、創(chuàng)建其父類的 ObjecStreamClass 實例、通過反射獲取當前類的所有字段信息缭乘、反射獲取是否自行實現(xiàn)了 writeObject()沐序、readObject() 、writeReplace()、readResolve() 等方法策幼。
下面我們就幾個關鍵方法做進一步說明:
- suid = getDeclaredSUID(cl) 獲取 serialVersionUID
系統(tǒng)通過反射的方式判斷我們是否有聲明 serialVersionUID:
private static Long getDeclaredSUID(Class<?> cl) {
try {
//通過反射獲取是否定義了serialVersionUID
Field f = cl.getDeclaredField("serialVersionUID");
int mask = Modifier.STATIC | Modifier.FINAL;
if ((f.getModifiers() & mask) == mask) {
//必須static final聲明
f.setAccessible(true);
//必須用static聲明邑时,否則獲取不到
return Long.valueOf(f.getLong(null));
}
} catch (Exception ex) {
}
return null;
}
從這里我們可以看出自定義 serialVersionUID 必須聲明為 static final。
默認 serialVersionUID 計算規(guī)則在 ObjectStreamClass computeDefaultSUID 方法特姐。序列化使用一個 hash晶丘,該 hash 是根據(jù)給定源文件中幾乎所有東西:方法名稱、字段名稱唐含、字段類型浅浮、訪問修改方法等-計算出來的,序列化將該 hash 值與序列化流中的 hash 值比較捷枯。
為了使 Java 運行時相信兩種類型實際上是一樣的滚秩,第二版和隨后版本的 Person 必須與第一版有相同的序列化版本 hash(存儲為 private static final serialVersionUID 字段)。因此铜靶,我們需要 serialVersionUID 字段叔遂,它是通過對原始(或 V1)版本的 Person 類運行 JDK serialver 命令計算出的。
一旦有了 Person 的 serialVersionUID争剿,不僅可以從原始對象 Person 的序列化數(shù)據(jù)創(chuàng)建 PersonV2 對象(當出現(xiàn)新字段時已艰,新字段被設為缺省值,最常見的是“null”)蚕苇,還可以反過來做:即從 PersonV2 的數(shù)據(jù)通過反序列化得到 Person哩掺。
整個默認 serialVersionUID 計算流程非常繁瑣和復雜,通常我建議顯示聲明會更加穩(wěn)妥涩笤,因為隱士聲明假如類發(fā)生一點點變化嚼吞,進行反序列化都會由于 serialVersionUID 改變而導致 InvalidClassException 異常。
- fields = getSerialFields(cl) 反射獲取當前類的所有字段
首先系統(tǒng)會判斷我們是否自行實現(xiàn)了字段序列化 serialPersistentFields 屬性蹬碧,否則走默認序列化流程舱禽,既忽律 static、transient 字段恩沽。
private static ObjectStreamField[] getSerialFields(Class<?> cl)
throws InvalidClassException {
ObjectStreamField[] fields;
//必須實現(xiàn)了Serialiazble誊稚,并且不是代理類,不是Externalizable,不是接口類型
if (Serializable.class.isAssignableFrom(cl)
&& !Externalizable.class.isAssignableFrom(cl)
&& !Proxy.isProxyClass(cl)
&& !cl.isInterface()) {
//是否聲明serialPersistentFields的自定義字段序列化規(guī)則
if ((fields = getDeclaredSerialFields(cl)) == null) {
//默認序列化字段規(guī)則
fields = getDefaultSerialFields(cl);
}
Arrays.sort(fields);
} else {
fields = NO_FIELDS;
}
return fields;
}
我們先來看下默認序列化流程的字段獲取規(guī)則:
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
//反射獲取當前類的所有字段
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
//忽略 static 或 transient 字段
int mask = Modifier.STATIC | Modifier.TRANSIENT;
for (int i = 0; i < clFields.length; i++) {
if ((clFields[i].getModifiers() & mask) == 0) {
//非static罗心,非transient字段
//將其封裝在ObjectStreamField中
//ObjectStreamField是對Field封裝
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
可以看到系統(tǒng)默認過濾掉 static里伯、transient 聲明的字段,將相關字段 Field 包裝在 ObjectStreamField 渤闷,ObjectStreamField 是對 Field 的封裝疾瓮,簡單看下它的構造方法:
ObjectStreamField(Field field, boolean unshared, boolean showType) {
//當前字段
this.field = field;
//是否可以被序列化
this.unshared = unshared;
//字段名稱
name = field.getName();
//字段類型
Class<?> ftype = field.getType();
//是基本類型還是引用類型
type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
//獲取字段類型的簽名
signature = getClassSignature(ftype).intern();
}
那該如何理解 serialPersistentFields 自行實現(xiàn)字段序列化規(guī)則呢?我們還是通過上面的的示例來了解下:
-
serialPersistentFields 自行實現(xiàn)字段序列化規(guī)則
public final class Person implements Serializable { private String name; private int age; private String like; private transient String features; private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("name", String.class), new ObjectStreamField("age", Integer.class), new ObjectStreamField("features", String.class) };
}
對 Person 做了下簡單修改飒箭,添加 serialPersistentFields 字段狼电,并指定了要序列化的字段名稱蜒灰,前面有分析道 Serializable 默認序列化規(guī)則會忽略 static、transient 聲明的字段肩碟。此時我們看下自行實現(xiàn)字段序列化規(guī)則卷员,看下它是如何實現(xiàn)的:
private static ObjectStreamField[] getDeclaredSerialFields(Class<?> cl)
throws InvalidClassException {
ObjectStreamField[] serialPersistentFields = null;
try {
//通過反射serialPersistentFields判斷是否聲明自定義字段序列化規(guī)則
Field f = cl.getDeclaredField("serialPersistentFields");
int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
if ((f.getModifiers() & mask) == mask) {
//必須聲明為private static final
f.setAccessible(true);
serialPersistentFields = (ObjectStreamField[]) f.get(null);
}
} catch (Exception ex) {
}
if (serialPersistentFields == null) {
return null;
} else if (serialPersistentFields.length == 0) {
return NO_FIELDS;
}
//保存需要序列化的Field字段
ObjectStreamField[] boundFields =
new ObjectStreamField[serialPersistentFields.length];
Set<String> fieldNames = new HashSet<>(serialPersistentFields.length);
for (int i = 0; i < serialPersistentFields.length; i++) {
ObjectStreamField spf = serialPersistentFields[i];
//獲取字段名稱
String fname = spf.getName();
if (fieldNames.contains(fname)) {
//不能存在重復的字段名
throw new InvalidClassException(
"multiple serializable fields named " + fname);
}
//不允許存在重復的字段名稱
fieldNames.add(fname);
//大量反射
try {
//根據(jù)字段名稱獲取對應的Field
Field f = cl.getDeclaredField(fname);
//自定義傳遞字段類型必須與實際類型一致
if ((f.getType() == spf.getType())
//不是static聲明字段
&& ((f.getModifiers() & Modifier.STATIC) == 0)) {
boundFields[i] =
new ObjectStreamField(f, spf.isUnshared(), true);
}
} catch (NoSuchFieldException ex) {
}
if (boundFields[i] == null) {
//反射獲取第i個字段失敗
boundFields[i] = new ObjectStreamField(
fname, spf.getType(), spf.isUnshared());
}
}
return boundFields;
}
serialPersistentFields 屬性可以幫助我們替換默認字段序列化流程,ObjectStreamField 類型數(shù)組定義了需要被序列化的字段腾务,而且該規(guī)則將會替代默認字段序列化規(guī)則聲明,否則將不會被自動序列化削饵。
此時 transitent 聲明字段也可以被添加進序列化規(guī)則岩瘦,但是 static 聲明字段仍然不可以:
((f.getModifiers() & Modifier.STATIC) == 0)
- 關于 serialPersistentFields 屬性簡單做下總結:
- serialPersistentFields 必須用 private static final 聲明;
- 該屬性會替換默認字段(非static窿撬、非transient)序列化規(guī)則启昧,此時需要序列化字段必須添加到屬性中;
- 被 transient 聲明字段也可以被添加進序列化中劈伴。
serialPersistentFields 相對來說提供的“支持”有限密末,比如我們我們要將相關敏感數(shù)據(jù)序列化之前先經過加密,反序列時自動對其解密跛璧,該如何更好的實現(xiàn)呢严里?writeObject() 和 readObject() 這兩個方法就可以幫我們一次性滿足。
- writeObject() 和 readObject() 方法作用
還是通過上面的例子追城,添加 writeObject() 和 readObject() 自定義序列化/反序列流程:
public final class Person implements Serializable {
private String name;
private int age;
private String like;
private void writeObject(java.io.ObjectOutputStream stream)
throws java.io.IOException {
//對age字段序列化之前進行加密
age = age << 2;
//繼續(xù)使用默認序列化流程
stream.defaultWriteObject();
//也可以直接
//stream.writeInt(age);
}
private void readObject(java.io.ObjectInputStream stream)
throws java.io.IOException, ClassNotFoundException {
stream.defaultReadObject();
//也可以使用
//stream.readInt();
//反序列化后對其解密
age = age << 2;
}
}
通過自行實現(xiàn)序列化 writeObject 和 反序列化 readObject 方法替換默認流程刹碾,我們可以對某些字段做些特殊修改,也可以實現(xiàn)序列化的加密功能座柱。
如果此時我們序列化對象的類型發(fā)生了變化時該如何處理呢迷帜?這時我們可以通過 writeReplace 和 readResolve 方法實現(xiàn)對序列化的版本兼容。接下來我們就聊聊 wirteReplace 和 readResolve 方法的作用色洞。
- writeReplace 和 readResolve 方法作用
還是通過上面的例子戏锹,做下改造進行說明:
public final class Person implements Serializable {
private String name;
private int age;
private String like;
private Person spouse;
public Person(String name, int age, String like) {
this.name = name;
this.age = age;
this.like = like;
}
private Object writeReplace()
throws java.io.ObjectStreamException {
return new PersonProxy(this);
}
public void setSpouse(Person value) {
spouse = value;
}
public Person getSpouse() {
return spouse;
}
//代理類
private class PersonProxy implements Serializable {
private String data;
public PersonProxy(Person person) {
this.data = person.name + "," + person.age + "," + person.like;
final Person spouse = person.spouse;
if (spouse != null) {
data = data + "," + spouse.name + "," + spouse.age + "," + spouse.like;
}
}
private Object readResolve()
throws java.io.ObjectStreamException {
String[] pieces = data.split(",");
final Person result = new Person(pieces[0], Integer.parseInt(pieces[2]), pieces[1]);
if (pieces.length > 3) {
result.setSpouse(new Person(pieces[3], Integer.parseInt
(pieces[5]), pieces[4]));
result.getSpouse().setSpouse(result);
}
return result;
}
}
}
writeReplace 和 readResolve 方法使 Person 類可以將它的所有數(shù)據(jù)(或其中的核心數(shù)據(jù))打包到一個 PersonProxy 中,將它放入到一個流中火诸,然后在反序列化時再進行解包锦针。
注意,PersonProxy 必須跟蹤 Person 的所有數(shù)據(jù)惭蹂。這通常意味著代理需要是 Person 的一個內部類伞插,以便能訪問 private 字段。有時候盾碗,代理還需要追蹤其他對象引用并手動序列化它們媚污,例如 Person 的 spouse。
這種技巧是少數(shù)幾種不需要讀/寫平衡的技巧之一廷雅。例如耗美,一個類被重構成另一種類型后的版本可以提供一個 readResolve 方法京髓,以便靜默地將被序列化的對象轉換成新類型。類似地商架,它可以采用 writeReplace 方法將舊類序列化成新版本堰怨。
-
Serializable 的序列化/反序列化的調用流程如下:
//序列化 E/test:SerializableTestData writeRepace E/test:SerializableTestData writeObject //反序列化 E/test:Serializable readObject E/test:Serializable readResolve
分析到這里不知道你是否有感受到,Serializable 整個計算過程非常復雜蛇摸,而且因為存在大量反射和 GC 的影響备图,序列化的性能會比較差。另外一方面因為序列化文件需要包含的信息非常多赶袄,導致它的大小比 Class 文件本身還要大很多揽涮,這樣又會導致 I/O 讀寫上的性能問題。
重新回到 ObjectOutputStream writeObject0 方法中饿肺,接著向下分析(詳細方法體已在上面貼出)蒋困,在拿到 ObjectStreamClass 實例后,首先判斷是否有自行實現(xiàn)的 writeReplace敬辣、并且該方法返回不為 null 與不是當前對象類型:
if (desc.hasWriteReplaceMethod()
&& (obj = desc.invokeWriteReplace(obj)) != null
&& (repCl = obj.getClass()) != cl) {
//如果實現(xiàn)了writeReplace方法替換掉原寫入對象 與
//該方法返回對象不為null 與
//返回對象類型不是當前序列化類型
cl = repCl;
//生成替換對象類型的ObjectStreamClass
desc = ObjectStreamClass.lookup(cl, true);
}
這里大家可以結合上面對 ObjectStreamClass 構造方法和示例進行理解雪标。
接著最后 writeOrdinaryObject 方法(這里只看主線流程):
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared) throws IOException {
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
//檢查ObjectStreamClass對象
desc.checkSerialize();
//寫入字節(jié)0x73
bout.writeByte(TC_OBJECT);
//寫入class信息
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
//是否實現(xiàn)了Externalizable與不是代理類型
writeExternalData((Externalizable) obj);
} else {
//寫入該對象變量信息及其父類的成員變量
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
checkSerialize 方法是檢查之前一系列流程是否發(fā)生過異常,否則將拋出異常溉跃。
這里我們重點跟蹤下 writeSerialData 方法:
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException {
//獲取當前類以及其父類所有實現(xiàn)Serializable的ObectStreamClass
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
//判斷是否重寫了writeObject()方法
//還記得在ObjectStreamClass構造方法中判斷當前類中是否定義了writeObject方法村刨,
//判斷writeObject method 是否為null
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
//反射調用重寫過的writeObject()方法
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
//否則走默認寫入規(guī)則
defaultWriteFields(obj, slotDesc);
}
}
}
注意看 for 循環(huán)遍歷所有的 ObjectStreamClass , 根據(jù)是否自行實現(xiàn)了 writeObject 方法走自行實現(xiàn)字段序列化規(guī)則還是走默認序列化規(guī)則 defaultWriteFields 方法喊积,關于自定義 writeObject 方法序列化規(guī)則烹困,上面通過示例做過分析。下面我們主要看下默認序列化規(guī)則 defaultWriteField 方法:
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException {
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
//obj不是cl類型實例
throw new ClassCastException();
}
//檢查中間過程是否有非法信息
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
//獲取當前類的所有字段信息
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
//返回獲取每個Field的值乾吻,保存在objVals中
//這里就是通過反射獲取到Field在當前obj中對應的值
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
//根據(jù)實際類型調用
//這里會遞歸調用writeObject0方法髓梅,
//writeObject0方法的最后根據(jù)實際類型寫入
//如果是引用類型,又會重復該過程
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
該類的所有字段已經保存在 ObjectStreamClass 中绎签,這里也是直接獲取到所有 Field枯饿,然后通過反射獲取到該 Field 在當前 obj 中的值,然后根據(jù)實際類型調用 writeObject0 方法诡必,注意如果是引用類型奢方,又會遞歸調用該過程。
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed
} 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) {
//我們只看實現(xiàn) Serializable 序列化過程
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
以寫入 writeString 方法為例看下:
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) {
bout.writeByte(TC_STRING);
bout.writeUTF(str, utflen);
} else {
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}
實際先寫入到 BlockDataOutputStream 緩存中(ObjectOutputStream 構造方法中創(chuàng)建爸舒,默認緩存大小是 1KB)蟋字,每當寫入內容超過該閾值:
public void write(int b) throws IOException {
if (pos >= MAX_BLOCK_SIZE) {
//超過最大最大可寫入此時要強制寫入
//outputStream
drain();
}
buf[pos++] = (byte) b;
}
void drain() throws IOException {
if (pos == 0) {
return;
}
if (blkmode) {
writeBlockHeader(pos);
}
//寫入到outputStream中
out.write(buf, 0, pos);
pos = 0;
warnIfClosed();
}
此時調用的就是傳入 ObjectOutputStream 構造方法的輸出流了。
分析到這里扭勉,關于 Serializable 序列化背后的實現(xiàn)原理就已經分析完了鹊奖,當然文中還省去了大部分實現(xiàn)細節(jié),感興趣的朋友可以進一步閱讀源碼進行分析涂炎。
Java 提供的 Serializable 對象序列化機制忠聚,遠比大多數(shù) Java 開發(fā)人員想象的更靈活设哗,這使我們有更多的機會解決棘手的情況。其實像這樣的編程妙招在 JVM 中隨處可見两蟀。關鍵是我們要知道它們网梢,在后續(xù)的項目中可以更好的實踐它們。
總結
Serializable 序列化支持替代默認流程赂毯,它會先反射判斷是否存在我們自己實現(xiàn)的序列化方法 writeObject 或 反序列化方法 readObject战虏。通過這兩個方法,我們可以對某些字段做一些特殊修改党涕,也可以實現(xiàn)序列化的加密功能活烙。
我們可以通過 writeReplace 和 readResolve 方法實現(xiàn)自定義返回的序列化實例。通過它們實現(xiàn)對序列化的版本兼容遣鼓,例如通過 readResolve 方法可以把老版本的序列化對象轉換成新版本的對象類型。
Serializable 整個序列化過程使用了大量的反射和臨時變量重贺,而且在序列化對象的時候骑祟,不僅會序列化當前對象本身,還需要遞歸序列化引用的其它對象气笙。
Serializable 使用注意事項
不被序列化字段次企。類的 static 變量以及被聲明為 transient 的字段,默認的序列化機制都會忽略該字段潜圃,不會進行序列化存儲缸棵。當然我們也可以使用進階的 writeObject 和 readObject 方法做自定義的序列化存儲。
serialVersionUID谭期。在類實現(xiàn)了 Serializable 接口后堵第,我們需要添加一個 Serial Version ID,它相當于類的版本號隧出。這個 ID 我們可以顯示聲明也可以讓編譯器自己計算踏志。通常建議顯示聲明會更加穩(wěn)妥,因為隱士聲明假如類發(fā)生了一點點變化胀瞪,進行反序列化都會由于 serialVersionUID 改變而導致 InvalidClassException 異常针余。
構造方法。Serializable 的反序列化默認是不會執(zhí)行構造函數(shù)的凄诞,它是根據(jù)數(shù)據(jù)流中對 Object 的描述信息創(chuàng)建對象的圆雁。如果一些邏輯依賴構造函數(shù),就可能會出現(xiàn)問題帆谍,例如一個靜態(tài)變量只在構造方法中賦值伪朽,當然我們也可以通過進階的自定義反序列化修改。
雖然 Serializalbe 性能那么差既忆,但是它也有一些進階的使用技巧驱负。不過在 Android 需要重新設計一套更加輕量且高效的機制嗦玖,感興趣可以繼續(xù)閱讀該系列下篇文章《Android 對象序列化之 Parcelable 取代 Serializable ?》
推薦參考資料
以上便是個人在學習 Serializable 時相關心得和體會跃脊,文中如有不妥或有更好的分析結果宇挫,歡迎指出。
文章如果對你有幫助酪术,就請留個贊吧器瘪!