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 啟用事務(wù)源碼分析》
《SQLite 數(shù)據(jù)庫 WAL 模式工作原理簡介》
《SQLite 數(shù)據(jù)庫鎖機(jī)制與事務(wù)簡介》
《SQLite 數(shù)據(jù)庫優(yōu)化那些事兒》
前言
對象序列化系列葡幸,主要內(nèi)容包括:Java 原生提供的 Serializable ,更加適合 Android 平臺輕量且高效的 Parcelable拓巧,以及追求性能完美的 Serial。該系列內(nèi)容主要結(jié)合源碼的角度分析它們各自的優(yōu)缺點以及合適的使用場景。
對象序列化系列赤兴,已經(jīng)分別為大家介紹了實現(xiàn)對象序列化的兩種不同方案:《Android 對象序列化之你不知道的 Serializable》和《Android 對象序列化之 Parcelable 取代 Serializable ?》横漏。
先來回顧下它們各自都有哪些優(yōu)缺點:
- Serializable
它是 Java 原生提供的序列化機(jī)制抠艾,在 Android 中也被廣泛使用≌锵兀可以通過它實現(xiàn)對象持久化存儲榜苫,也可以通過 Bundle 傳遞 Serializable 的序列化數(shù)據(jù)。
Serializable 的原理是通過 ObjectInputStream 和 ObjectOutputStream 來實現(xiàn)翎冲,整個序列化過程使用了大量的反射和臨時變量,而且還需要遞歸序列化對象引用的其它對象媳荒】购罚可以說整個過程計算非常復(fù)雜,而且因為存在大量反射和 GC 的影響钳枕,序列化的性能會比較差缴渊。雖然 Serializable 性能那么差,但是它也有一些進(jìn)階的使用技巧鱼炒,如序列化加密衔沼、版本替換等。
- Parcelable
由于 Java 的 Serializable 的性能較低昔瞧,Android 需要重新設(shè)計一套更加輕量且高效的對象序列化和反序列化機(jī)制指蚁。Parcelable 正是這個背景下產(chǎn)生的,它的核心作用就是為了解決 Android 中大量跨進(jìn)程通信的性能問題自晰。
另外你可以發(fā)現(xiàn) Parcelable 序列化和 Java 的 Serializable 序列化差別還是比較大的凝化,Parcelable 只會在內(nèi)存中進(jìn)行序列化操作,并不會將數(shù)據(jù)存儲到磁盤里酬荞。
在時間開銷和使用成本的權(quán)衡上搓劫,Parcelable 與 Serializable 的選擇截然不同,Serializable 更注重使用成本混巧,而 Parcalable 機(jī)制選擇的是性能優(yōu)先枪向。說道這里,如果你對它們還不熟悉的話咧党,可以先去參考下秘蛔。
那有沒有更好的方案來解決 Serializalbe 和 Parcleable 的痛點呢?今天我們就來了解一個開源且高性能的序列化方案凿傅。
Serial
作為程序員冤荆,我們肯定會追求完美。那有沒有性能更好的方案并且可以解決這些痛點呢柱查?
事實上辱志,關(guān)于序列化基本每個大公司都會有自己自研的一套方案,這里推薦 Twitter 開源的高性能序列化方案 Serial箱残。
Serial 力求幫助開發(fā)者實現(xiàn)高性能和高可控的序列化過程滔迈。這個序列化框架提供了一個名叫 Serializers 序列化器止吁,開發(fā)者可以通過它來明確地定義對象的序列化過程。
Serial 集成
從 Maven Central 上下載最新的依賴
repositories {
mavenCentral()
}
dependencies {
compile 'com.twitter.serial:serial:0.1.6'
}
Serial 方案的主要優(yōu)點如下:
- 相比起傳統(tǒng)的反射序列化方案更加高效(沒有使用反射)
- 性能相比傳統(tǒng)方案提升了 3 倍(序列化的速度提升了 5 倍燎悍,反序列化提升了 2.5 倍)
- 序列化生成的數(shù)據(jù)量(byte[])大約是之前的 1/5
- 開發(fā)者對于序列化過程的控制較強(qiáng)敬惦,可定義哪些 Object,F(xiàn)ield 需要被序列化
- 有很強(qiáng)的 debug 能力谈山,可以調(diào)試序列化的過程
那它是否真的是高性能呢俄删?我們可以將它和前面的兩套方案做一個對比測試。
... | 序列化時間/ms | 反序列化時間/ms | 文件大小/Byte |
---|---|---|---|
Serializable | 162 | 79 | 427932 |
Parcelable | 61 | 30 | 755564 |
Serial | 57 | 18 | 357528 |
Serial 使用
Serializer 是本庫中的關(guān)鍵類奏路,這個類提供了序列化和反序列化的能力畴椰,序列化的定義和流程都是通過它來實現(xiàn)的。
目前庫中默認(rèn)提供的序列化實現(xiàn)類是 ByteBufferSerial鸽粉,它的產(chǎn)物是 byte[]斜脂。使用者也可以自行更換實現(xiàn)類,不用拘泥于 byte[]触机。
定義 Serializer
- 于之前的實現(xiàn) Serializable 接口不同帚戳,這里需要給每個被序列化的對象單獨定義一個 Serializer。
- Serializers 中需要給每個 field 明確定義 write 和 read 操作儡首,對于有繼承關(guān)系的序列化類片任,需要被遞歸的進(jìn)行定義
- Serializers 已經(jīng)為使用者處理了空對象問題,就像 read/writeString 一樣椒舵,記住不要使用原始的方法
- Serializers 是無狀態(tài)的蚂踊,所以我們可以將其寫為 Object 的內(nèi)部類,并通過 SERIALIZER 作為名稱來訪問它笔宿。
對于大多數(shù)類犁钟,你可以建立一個繼承自 ObjectSerializer 的子類,然后實現(xiàn) serializeObject 方法和 deserializeObject 方法:
public static class ExampleObject {
public static final ObjectSerializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer();
public final int num;
public final SubObject obj;
public ExampleObject(int num, @NotNull SubObject obj) {
this.num = num;
this.obj = obj;
}
...
private static final class ExampleObjectSerializer extends ObjectSerializer<ExampleObject> {
@Override
protected void serializeObject(@NotNull SerializationContext context, @NotNull SerializerOutput output,
@NotNull ExampleObject object) throws IOException {
output
.writeInt(object.num) // first field
.writeObject(object.obj, SubObject.SERIALIZER); // second field
}
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt(); // first field
final SubObject obj = input.readObject(SubObject.SERIALIZER); // second field
return new ExampleObject(num, obj);
}
}
}
將 ExampleObject 對象序列化為 byte[]
final Serial serial = new ByteBufferSerial();
final byte[] serializedData = serial.toByteArray(object, ExampleObject.SERIALIZER)
將對象從 byte[] 反序列化為 Object
final ExampleObject object = serial.fromByteArray(serializedData, ExampleObject.SERIALIZER)
在 ExampleObject 中內(nèi)部類和 Parcelable.Creator 極為相似泼橘,都是按照順序?qū)ψ兞窟M(jìn)行讀寫操作涝动。接下來我們就從源碼的角度分析下 Serial 實現(xiàn)序列化的原理。在分析的過程中我們希望能夠更多的與 Android 系統(tǒng)提供的 Parcelable 做對比炬灭,看下 Serial 實現(xiàn)更加高效序列化機(jī)制都做了哪些優(yōu)化醋粟?
源碼分析
Serial 序列化過程分析
從 ByteBufferSerial 的 toByteArray 方法開始:
serial.toByteArray(object, ExampleObject.SERIALIZER)
將 Objecdt 轉(zhuǎn)換成 byte[] ,ByteBufferSerial 的 toByteArray 方法:
@Override
@NotNull
public <T> byte[] toByteArray(@Nullable T value, @NotNull Serializer<T> serializer)
throws IOException {
if (value == null) {
//空字節(jié)數(shù)組
return InternalSerialUtils.EMPTY_BYTE_ARRAY;
}
final Pools.SynchronizedPool<byte[]> currentPool = mBufferPool;
//獲取復(fù)用的byte[]
final byte[] tempBuffer = currentPool != null ? currentPool.acquire() : null;
//默認(rèn)mBufferPool為null
if (tempBuffer != null) {
try {
synchronized (tempBuffer) {
return toByteArray(value, serializer, tempBuffer);
}
} finally {
//回收該byte[]
currentPool.release(tempBuffer);
}
}
//默認(rèn)mBufferPool為null重归,此時走這里
return toByteArray(value, serializer, null);
}
ByteBufferSerial 內(nèi)部可以指定一個 byte[] 的復(fù)用池 Pools.SynchronizedPool米愿。有助于減少頻繁內(nèi)存空間申請可能帶來的問題。
繼續(xù)向下分析鼻吮,實際調(diào)用了 toByteArray 的重載方法:
@NotNull
public <T> byte[] toByteArray(@Nullable T value, @NotNull Serializer<T> serializer,
@Nullable byte[] tempBuffer) throws IOException {
if (value == null) {
//空字節(jié)數(shù)組
return InternalSerialUtils.EMPTY_BYTE_ARRAY;
}
//真正開始完成序列化的是ByteBufferSerializerOutput
final ByteBufferSerializerOutput serializerOutput = new ByteBufferSerializerOutput(tempBuffer);
try {
//將ByteBufferSerializerOutput作為參數(shù),
//此時回到ExampleObjectSerializer的serializeObject方法
serializer.serialize(mContext, serializerOutput, value);
} catch (IOException e) {
throw e;
}
//返回序列化后的字節(jié)數(shù)組
//ByteBufferSerializerOutput內(nèi)部是通過ByteBuffer完成序列化
return serializerOutput.getSerializedData();
}
注意參數(shù) serializer 就是在上面例子 ExampleObject 中定義的 ExampleObjectSerializer 實例育苟,然后創(chuàng)建 ByteBufferSerializerOutput 實例作為 serialize 方法的參數(shù):
try {
//將ByteBufferSerializerOutput作為參數(shù),
//此時回到ExampleObjectSerializer的serializeObject方法
serializer.serialize(mContext, serializerOutput, value);
} catch (IOException e) {
throw e;
}
ExampleObjectSerializer 繼承自 ObjectSerializer,每個需要序列化的對象都需要為其自定義一個 ObjectSerializer椎木,這也是 Serial 提供的序列化規(guī)則违柏,實際與 Android 系統(tǒng)提供的 Parcelable.Creator 機(jī)制類似博烂。
所以這里先調(diào)用了 ObjectSerialize 的 serialize 方法:
@Override
public final void serialize(@NotNull SerializationContext context,
@NotNull SerializerOutput output, @Nullable T object) throws IOException {
//object不為null
if (!SerializationUtils.writeNullIndicator(output, object)) {
//序列化版本管理
if (context.isDebug()) {
output.writeObjectStart(mVersionNumber, getClass().getSimpleName());
} else {
output.writeObjectStart(mVersionNumber);
}
//noinspection BlacklistedMethod
//這里調(diào)用重寫了該方法ExampleObjectSerializer中
serializeObject(context, output, object);
output.writeObjectEnd();
}
}
//serializeObject必須由子類實現(xiàn)
protected abstract void serializeObject(@NotNull SerializationContext context,
@NotNull SerializerOutput output, @NotNull T object) throws IOException;
Serial 默認(rèn)為我們提供了序列化版本管理,這有區(qū)別與 Android 的 Parcelable漱竖,我們可以通過該版本號實現(xiàn)序列化的版本兼容禽篱,后面會為大家介紹該部分內(nèi)容。
serializeObject 方法開始真正自定義序列化過程馍惹,這個過程其實和 Parcelable 中的 Parcelable.Creator 極為相似躺率,都是按照順序?qū)ψ兞窟M(jìn)行讀寫,為了方便理解万矾,可以和 Parcelable.Creator 做下類比:
public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
Person person = new Person();
person.mName = source.readString();
person.mSex = source.readString();
person.mAge = source.readInt();
return person;
}
//供反序列化本類數(shù)組時調(diào)用的方法
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
通過上面的分析我們知道 SerializerOutput 的實際類型是 ByteBufferSerializerOutput肥照,這也是我們要重點分析的序列化實現(xiàn)過程:
ByteBufferSerializerOutput 中提供了一系列的 write 操作,我們先通過 output.writeInt 方法看下:
@Override
@NotNull
public ByteBufferSerializerOutput writeInt(int val) {
//SerializerDefs.TYPE_INT byte類型勤众,值為2
writeIntHeader(SerializerDefs.TYPE_INT, val);
return this;
}
從方法的返回其實可以看出 ByteBufferSerializerOutput 設(shè)計為構(gòu)建者模式,可以更方便的操作鲤脏。
繼續(xù)向下看 writeIntHeader 方法:
/**
* int 類型劃分成4種方式存儲
* 0 默認(rèn)不寫入value们颜,只寫入類型 defualt類型,默認(rèn)值為0
* 1~255猎醇,僅用一個字節(jié)表示窥突,類型 byte
* 256 ~ 65535 僅用2個字節(jié)表示,類型short
* 大于65535 再用4個字節(jié)表示硫嘶,類型int
*/
private void writeIntHeader(byte type, int val) {
if (val == 0) {
//ByteBufferSerializerDefs.SUBTYPE_DEFAULT)) 為byte類型值為1
//makeHeader得到的值是:01000001
writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_DEFAULT));
} else if ((val & 0xFFFFFF00) == 0) {
//不超過一個字節(jié)的取值范圍 1~255
//0x00000001~0x000000FF & 0XFFFFFF00 = 0 此時的值<=255(一個字節(jié)空間足以)
//<= 255
writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_BYTE));
//一個字節(jié)空間阻问,確保剩余空間
ensureCapacity(ByteBufferSerializerDefs.SIZE_BYTE);
mByteBuffer.put((byte) val);
} else if ((val & 0xFFFF0000) == 0) {
//不超過2個字節(jié)的取值范圍256~65535
//2個字節(jié)的最大取值是65535
writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_SHORT));
//2個字節(jié),確保剩余空間
ensureCapacity(ByteBufferSerializerDefs.SIZE_SHORT);
//short 2個字節(jié)空間
mByteBuffer.putShort((short) val);
} else {
//否則四個字節(jié)空間 2^32 - 1
writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_INT));
//4個字節(jié)沦疾,確保剩余空間充足
ensureCapacity(ByteBufferSerializerDefs.SIZE_INT);
mByteBuffer.putInt(val);
}
}
在 writeIntHeader 方法中称近,寫入 int 值按照取值范圍被劃分成 4 種類型:
- 寫入值為 0
- 寫入值在 1 ~ 255 之間(1個字節(jié))
- 寫入值在 256 ~ 65535 之間(2個字節(jié))
- 寫入值在 65536 ~ 2^32 -1(4個字節(jié))
可以看出 Serial 根據(jù)具體寫入值范圍來優(yōu)化字節(jié)存儲空間。
先來看下 writeHeader 方法:
private void writeHeader(byte headerType) {
//確保剩余空間充足
//擴(kuò)容機(jī)制是原容量*2
ensureCapacity(ByteBufferSerializerDefs.SIZE_BYTE);
//表示當(dāng)前寫入值的類型哮塞,default/byte/short/int 注意default將不再寫入value,直接默認(rèn)值0
mByteBuffer.put(headerType);
}
writeHeader 方法是為了確定當(dāng)前寫入值的類型刨秆,用于在反序列化時根據(jù)數(shù)據(jù)類型讀取合適的字節(jié)空間。
ensureCapacity 方法是保證當(dāng)前剩余空間大于要寫入字節(jié)數(shù)忆畅,默認(rèn)擴(kuò)容該機(jī)制是原容量 * 2衡未。
/**
* 這里單位是字節(jié),如10字節(jié)
*/
private void ensureCapacity(int sizeNeeded) {
//剩余空間小于需要的空間家凯,單位字節(jié)
if (mByteBuffer.remaining() < sizeNeeded) {
//當(dāng)前位置
final int position = mByteBuffer.position();
final byte[] bufferContents = mByteBuffer.array();
//擴(kuò)容原容量的2倍
final byte[] newBufferContents = new byte[2 * mByteBuffer.capacity()];
//將原來的字節(jié)數(shù)中拷貝擴(kuò)容后的數(shù)組中
System.arraycopy(bufferContents, 0, newBufferContents, 0, position);
final ByteBuffer newBuffer = ByteBuffer.wrap(newBufferContents);
newBuffer.position(position);
mByteBuffer = newBuffer;
//擔(dān)心仍然不夠
ensureCapacity(sizeNeeded);
}
}
重新回到 writeIntHeader 方法
- 寫入值范圍 val == 0
當(dāng)寫入值 val == 0 時缓醋,我們看到 serial 只是寫入了一個字節(jié)的 Header,用以表示當(dāng)前數(shù)據(jù)的類型绊诲。并沒有將 val 的值在寫入送粱。
- 寫入值范圍:(val & 0xFFFFFF00) == 0
此時表示變量 val 的取值在 1 ~ 255(0 不會走到這里) 之間,僅用一個字節(jié)便可以表示驯镊。
- 寫入值范圍:(val & 0xFFFF0000) == 0
此時表示 val 的取值在 256 ~ 65535 之間葫督,用兩個字節(jié)空間表示足以竭鞍。
- 否則就是 4 個字節(jié)空間
不知道分析到這里,大家是否還記得 Parcelable 的序列化規(guī)則:在 Parcelable 中用于表示數(shù)據(jù)類型采用的是 int橄镜,相比起 Serial 要多占用 3 個字節(jié)空間偎快,并且 Serial 根據(jù)寫入值的范圍進(jìn)一步節(jié)約字節(jié)空間,這個優(yōu)化其實非常有效洽胶,因為在實際的項目中用到的絕大多數(shù)整數(shù)值都相對較小(絕大多數(shù) 2 個字節(jié)空間足以)晒夹。
而這樣的優(yōu)化在 Serial 中到處可見:
/**
* long 被劃分成2種
*/
private void writeLongHeader(byte type, long val) {
if ((val & 0xFFFFFFFF00000000L) == 0) {
//實際這里又根據(jù)值劃分為4種,結(jié)合上面分析的writeIntHeader
writeIntHeader(type, (int) val);
} else {
//否則8個字節(jié)
writeHeader(ByteBufferSerializerDefs.makeHeader(type, ByteBufferSerializerDefs.SUBTYPE_LONG));
ensureCapacity(ByteBufferSerializerDefs.SIZE_LONG);
mByteBuffer.putLong(val);
}
}
分析到這里其實我們也驗證了前面講到 Serial 主要優(yōu)點之一的:序列化生成的數(shù)據(jù)量(byte[])大約是之前的 1/5姊氓。
Serial 在整個序列化過程中的確與 Parcelable 極其類似丐怯,都沒有像 Java 原生提供的 Serializable 那樣使用大量的反射和臨時變量(Serializable 機(jī)制選擇的是使用成本優(yōu)先),Serial 和 Parcelable 在性能和使用成本的權(quán)衡上翔横,都選擇了性能優(yōu)先读跷,這樣對于開發(fā)人員在使用上就復(fù)雜了很多。 Serial 在序列化的過程中對字節(jié)的控制更加高效禾唁。相比 Parcelable 更節(jié)省空間效览。
以上便是 Serial 整個序列化過程,接下來我們就分析下 Serial 的反序列化過程荡短。
Serial 反序列化過程分析
還是結(jié)合上面給出的示例:
final ExampleObject object = serial.fromByteArray(serializedData, ExampleObject.SERIALIZER)
調(diào)用 ByteBufferSerial 的 fromByteArray 方法:
@Override
@Nullable
@Contract("null, _ -> null")
public <T> T fromByteArray(@Nullable byte[] bytes, @NotNull Serializer<T> serializer)
throws IOException,
ClassNotFoundException {
if (bytes == null || bytes.length == 0) {
//直接返回null
return null;
}
//序列化是ByteBufferSerializerOutput
//反序列化是ByteBufferSerializerInput
final SerializerInput serializerInput = new ByteBufferSerializerInput(bytes);
try {
//將ByteBufferSerializerInput作為參數(shù)調(diào)用ObjectSerializer的deserialize方法
//最終調(diào)用到自定義ExampleObjectSerializer的deserializeObject方法
return serializer.deserialize(mContext, serializerInput);
} catch (IOException | ClassNotFoundException | IllegalStateException e) {
throw new SerializationException("Invalid serialized data:\n" +
SerializationUtils.dumpSerializedData(bytes, serializerInput.getPosition(), mContext.isDebug()), e);
}
}
序列化的操作者是 ByteBufferSerializerOutput丐枉,對應(yīng)反序列化的操作者 ByteBufferSerializerInput。將其作為參數(shù)調(diào)用 ObjectSerializer 的 deserialize 方法掘托。
@Nullable
@Override
public T deserialize(@NotNull SerializationContext context, @NotNull SerializerInput input)
throws IOException, ClassNotFoundException {
if (SerializationUtils.readNullIndicator(input)) {
//如果byte[]為null瘦锹,或無數(shù)據(jù)直接返回null
return null;
}
//獲取序列化版本號
final int deserializedVersionNumber = input.readObjectStart();
if (deserializedVersionNumber > mVersionNumber) {
//如果序列化版本號大于當(dāng)前版本號則拋出異常
throw new SerializationException("Version number found (" + deserializedVersionNumber + ") is " +
"greater than the maximum supported value (" + mVersionNumber + ")");
}
//回到自定義的ExampleObjectSerializer的deserializeObject方法
final T deserializedObject = deserializeObject(context, input, deserializedVersionNumber);
input.readObjectEnd();
return deserializedObject;
}
最后實際回到自定義 ExampleObjectSerializer 的 deserializeObject 方法中:
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt(); // first field
final SubObject obj = input.readObject(SubObject.SERIALIZER); // second field
return new ExampleObject(num, obj);
}
其實這個過程與序列化過程基本一致。只不過這里是從 byte[] 還原到 Object 過程闪盔。
分析到這里弯院,大家是否還記得 Parcelable 的反序列過程:
雖然相比 Java 的 Serializable 減少使用反射提升整體序列化/反序列化性能。但是 Parcelable 在反序列化的過程中仍然使用到了少許反射:首次創(chuàng)建該 Parcelable 時泪掀,首先根據(jù) name 通過 Classloader 加載返回 Class 對象抽兆,然后反射獲取其內(nèi)部的 CREATOR 對象 ,然后在通過該 Creator 實例完成反序列化過程族淮,當(dāng)然后續(xù)系統(tǒng)會緩存該 Parcelable 的 Creator 實例辫红。
Serial 則徹底沒有使用任何反射,其內(nèi)部通過使用者手動傳入對應(yīng)的 Serializer 序列化/反序列化器祝辣。正如 Serial 介紹其主要優(yōu)點之一的:性能相比起傳統(tǒng)反射序列化方案更加高效(沒有使用反射)贴妻,性能比傳統(tǒng)方案提升了 3 倍(序列化速度提升了 5 倍,反序列化提升了 2.5 倍)蝙斜。
重新回到 deserializeObject 方法內(nèi) input.readInt 方法:
@Override
public int readInt() throws IOException {
//SerializerDefs.TYPE_INT表示int類型
return readIntHeader(SerializerDefs.TYPE_INT);
}
private int readIntHeader(byte expectedType) throws IOException {
//讀取數(shù)據(jù)類型信息
final byte subtype = readHeader(expectedType);
return readIntValue(subtype);
}
readHeader 方法用于確定當(dāng)前數(shù)據(jù)的類型名惩,還記得序列化時的 writeHeader 方法嗎,此時通過寫入數(shù)據(jù)類型確定要讀取的字節(jié)數(shù)孕荠,重點看下 readIntValue 方法:
/**
* 這里就是根據(jù)寫入時數(shù)據(jù)類型讀取相應(yīng)的字節(jié)數(shù)
*/
private int readIntValue(byte subtype) throws IOException {
try {
if (subtype == ByteBufferSerializerDefs.SUBTYPE_DEFAULT) {
//default默認(rèn)就是0娩鹉,在writeInt時攻谁,也是只寫了Header,并且沒有寫value
return 0;
} else if (subtype == ByteBufferSerializerDefs.SUBTYPE_BYTE) {
//byte類型讀取一個字節(jié)
//& 0xFF 的作用是:byte轉(zhuǎn)換成int弯予,要進(jìn)行補(bǔ)位(byte 8位戚宦,int 32位),補(bǔ)位高24為都為1
//此時數(shù)值已經(jīng)不一致了锈嫩,通過& 0xFF運(yùn)算保證高24位為0受楼,低8位保持原樣
return mByteBuffer.get() & 0xFF;
} else if (subtype == ByteBufferSerializerDefs.SUBTYPE_SHORT) {
//short類型讀取兩個字節(jié)
//& 0xFFFF的作用參照上面0xFF,保證高16位為0,低16位保持原樣
return mByteBuffer.getShort() & 0xFFFF;
} else {
//否則4個字節(jié)
return mByteBuffer.getInt();
}
} catch (BufferUnderflowException ignore) {
throw new EOFException();
}
}
readIntValue 方法完全對應(yīng)序列化時的 writeIntValue 劃分進(jìn)行字節(jié)數(shù)讀取呼寸。
這里需要說的是:& 0xFF 的作用艳汽,當(dāng) byte 強(qiáng)轉(zhuǎn)為 int 時,由于 byte 占用 8 位对雪,而 int 占用 32 位河狐,此時需要補(bǔ)充高位的 24 位為 1,這樣補(bǔ)碼后的二進(jìn)制位就不一致了瑟捣,此時通過 & 0xFF 保證高 24 位為 0甚牲,低 8 位保持原樣,這就就保證了二進(jìn)制數(shù)據(jù)的一致性蝶柿。
小結(jié)
至此關(guān)于 Serial 的序列化/反序列化主線工作流程就分析完了,感興趣的朋友可以進(jìn)一步深入源碼分析非驮。
從 Serial 的實現(xiàn)原理上看交汤,它與 Android 的 Parcelable 及其相似,不過相對 Parcelable 在寫入數(shù)據(jù)做了進(jìn)一步的空間優(yōu)化劫笙,而且整個過程沒有使用一點反射芙扎。
其他
較強(qiáng)的 Debug 能力
Serial 具有較強(qiáng)的 Debug 能力,可以調(diào)試序列化的過程 SerializationUtils
- dumpSerialzedData 會根據(jù)序列化后的 byte[] 數(shù)據(jù)產(chǎn)生 String 類型的 Log填大。
public static String dumpSerializedData(@NotNull byte[] bytes) {
return dumpSerializedData(bytes, -1, true);
}
@NotNull
public static String dumpSerializedData(@NotNull SerializerInput input, int position, boolean includeValues) {
final StringBuilder builder = new StringBuilder().append('{').append(InternalSerialUtils.lineSeparator());
try {
int objectNesting = 0;
String indentation = " ";
boolean addPositionMarker = position >= 0;
byte type;
while ((type = input.peekType()) != SerializerDefs.TYPE_EOF) {
if (type == SerializerDefs.TYPE_END_OBJECT) {
--objectNesting;
if (objectNesting < 0) {
throw new SerializationException("Object end with no matching object start.");
}
indentation = InternalSerialUtils.repeat(" ", objectNesting + 1);
input.readObjectEnd();
builder.append(indentation).append('}');
} else {
builder.append(indentation);
switch (type) {
case SerializerDefs.TYPE_BYTE: {
final byte b = input.readByte();
if (includeValues) {
builder.append("Byte: ").append(b);
} else {
builder.append("Byte");
}
break;
}
case SerializerDefs.TYPE_INT: {
final int i = input.readInt();
if (includeValues) {
builder.append("Integer: ").append(i);
} else {
builder.append("Integer");
}
break;
}
// ... 省略
}
}
Serial 最終會將序列化后的 byte[] 數(shù)據(jù)封裝進(jìn) StringBuilder 中進(jìn)行輸出戒洼。
- validataSerializedData 確保了序列化后的對象有有效的結(jié)構(gòu)(比如每個對象都有開頭和結(jié)尾)
部分源碼:
private static void readStream(@NotNull SerializerInput input, boolean singleObject) throws IOException {
int objectNesting = 0;
byte type;
while ((type = input.peekType()) != SerializerDefs.TYPE_EOF) {
switch (type) {
case SerializerDefs.TYPE_START_OBJECT:
case SerializerDefs.TYPE_START_OBJECT_DEBUG: {
input.readObjectStart();
//TYPE_START_OBJECT 與 TYPE_END_OBJECT 成對出現(xiàn)
++objectNesting;
break;
}
case SerializerDefs.TYPE_END_OBJECT: {
--objectNesting;
input.readObjectEnd();
if (singleObject && objectNesting == 0) {
return;
}
if (objectNesting < 0) {
throw new SerializationException("Object end with no matching object start.");
}
break;
}
default: {
throw new SerializationException("Unknown type: " + SerializerDefs.getTypeName(type) + '.');
}
}
}
if (objectNesting > 0) {
throw new SerializationException("Object start with no matching object end.");
}
}
Serial 的異常信息中會包含很多序列化失敗的原因,比如期望的類型和實際類型不匹配這種常見錯誤允华。
Serial 版本控制
如果我們在新版本 App 中添加或刪除了之前已經(jīng)被序列化的對象的 Field圈浇,那么在反序列化老版本數(shù)據(jù)的時候可能會碰到一些問題。
Serial 為我們提供了幾種方案來處理這種情況:
- OptionalFieldException
還是結(jié)合上面的例子靴寂,在 ExampleObject 添加了一個新的字段磷蜀,這時反序列化老版本 ExampleObject 就會出問題。Serializer 默認(rèn)會依次讀取所有的 Field百炬,此時拋出 OptionalFieldException 異常褐隆。
如果使用的是普通的 Serializer,我們可以通過 try-catch 來處理這個問題剖踊。
- 比如想要給 ExampleObject 的最后增加一個叫 name 字段(原先的 ExampleObject 僅有 num 和 SubObject 這兩個字段)
此時我們必須向下面一樣修改deserializeObject 方法:
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt();
final SubObject obj = input.readObject(SubObject.SERIALIZER);
final String name;
try {
name = input.readString();
} catch (OptionalFieldException e) {
name = DEFAULT_NAME; // 老版本中沒有這個字段庶弃,給它一個默認(rèn)值
}
return new ExampleObject(num, obj, name);
}
對于 BuilderSerializer衫贬,只需要在 deserializeToBuilder 的最后添加 .setName(input.readString()) 即可。
- 版本號
還可以通過給 Serializer 添加一個版本號歇攻,這樣在反序列化的過程中就可以通過這個版本號進(jìn)行復(fù)雜的處理了固惯。添加版本號十分簡單,只需要在 SERIALIZER 的構(gòu)造函數(shù)中傳入數(shù)字即可掉伏。
我們修改下上面的代碼缝呕,通過版本號來處理字段新老版本的問題:
final Serializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer(1);
...
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt();
final SubObject obj = input.readObject(SubObject.SERIALIZER);
final String name;
if (versionNumber < 1) {
name = DEFAULT_NAME;
} else {
name = input.readString();
}
return new ExampleObject(num, obj, name);
}
如果你刪除了序列化對象中部的某個 Field,比如 ExampleObject 中間的 SubObject斧散。你可能需要用 SerializationUtils.skipObject() 來終止整個反序列化過程供常。如果已經(jīng)把 SubObject 完全移除了,那么可以不用保留 SubObject 中的 Serializer 對象鸡捐。
比如栈暇,你可能在新版本中刪除了 SubObject,而老版本的數(shù)據(jù)中含有這個對象箍镜,你可以進(jìn)行下面的處理:
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber)
throws IOException, ClassNotFoundException {
final int num = input.readInt();
if (versionNumber < 1) {
SerializationUtils.skipObject()
name = DEFAULT_NAME;
} else {
name = input.readString();
}
return new ExampleObject(num, name);
}
另一種方法是調(diào)用 input.peekType源祈,這個方法可以讓你在讀取 Object 對象前進(jìn)行下一個參數(shù)的類型檢查,它提供了一個除判斷版本號之外的解決新老數(shù)據(jù)問題的方案色迂。當(dāng)你不愿意升級版本號或是不愿意擦除數(shù)據(jù)的時候香缺,這個方法會十分有用。
需要注意的是:該方法僅僅適用于兩個對象類型不同的情況歇僧。因為這里 Object 是 SubObject图张,name 類型是 String,所以可以進(jìn)行如下處理:
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt();
if (input.peekType() == SerializerDefs.TYPE_START_OBJECT) {
SerializationUtils.skipObject();
name = DEFAULT_NAME;
} else {
name = input.readString();
}
return new ExampleObject(num, name);
}
總結(jié)
不知大家有沒有注意到诈悍,Serial 從實現(xiàn)原理上看就像是把 Parcelable 和 Serializable 的優(yōu)點集合在一起的方案祸轮。
- 由于沒有使用反射,相比起傳統(tǒng)的反射序列話方案更加高效侥钳,具體你可以參考上面的測試數(shù)據(jù)适袜。
- 開發(fā)者對于序列化過程的控制較強(qiáng),可定義哪些 Object舷夺、Field 需要被序列化苦酱。
- 有很強(qiáng)的 Debug 能力,可以調(diào)試序列化的過程
- 有很強(qiáng)的版本管理能力给猾,可以通過版本號和 OptionalFieldException 做兼容躏啰。
總體看 Serial 這套對象序列化方案還不錯,但是對象的序列化要記錄的信息還是比較多耙册,在操作比較頻繁時候给僵,對應(yīng)用的影響還是不少的,這個時候我們可以選擇使用數(shù)據(jù)的序列化。
以上便是個人在學(xué)習(xí) Serial 時的心得和體會帝际,文中分析如有不妥或更好的分析結(jié)果蔓同,還請大家指出!
文章如果對你有幫助蹲诀,就請留個贊吧斑粱!