下面介紹三個數(shù)據(jù)序列化的候選方案:
? Protocal Buffers:強(qiáng)大丈挟,靈活,但是對內(nèi)存的消耗會比較大锐墙,并不是移動終端上的最佳選擇礁哄。
? Nano-Proto-Buffers:基于Protocal长酗,為移動終端做了特殊的優(yōu)化溪北,代碼執(zhí)行效率更高,內(nèi)存使用效率更佳夺脾。
? FlatBuffers:這個開源庫最開始是由Google研發(fā)的之拨,專注于提供更優(yōu)秀的性能。
FlatBuffers 是一個開源的跨平臺數(shù)據(jù)序列化庫咧叭,可以應(yīng)用到幾乎任何語言(C++, C#, Go, Java, JavaScript, PHP, Python)蚀乔,最開始是 Google 為游戲或者其他對性能要求很高的應(yīng)用開發(fā)的。項(xiàng)目地址在 GitHub 上菲茬。官方的文檔在 這里吉挣。
基本原理
如官方文檔的介紹,F(xiàn)latBuffers 就像它的名字所表示的一樣婉弹,就是把結(jié)構(gòu)化的對象睬魂,用一個扁平化(Flat)的緩沖區(qū)保存,簡單的來說就是把內(nèi)存對象數(shù)據(jù)镀赌,保存在一個一維的數(shù)組中氯哮。借用 Facebook 文章2的一張圖如下:
可見,F(xiàn)latBuffers 保存在一個 byte 數(shù)組中商佛,有一個“支點(diǎn)”指針(pivot point)以此為界喉钢,存儲的內(nèi)容分為兩個部分:元數(shù)據(jù)和數(shù)據(jù)內(nèi)容。其中元數(shù)據(jù)部分就是數(shù)據(jù)在前面良姆,其長度等于對象中的字段數(shù)量肠虽,每個 byte 保存對應(yīng)字段內(nèi)容在數(shù)組中的索引(從支點(diǎn)位置開始計(jì)算)。
如圖玛追,上面的 Person 對象第一個字段是 name舔痕,其值的索引位置是 1,所以從索引位置 1 開始的字符串,就是 name 字段的值 "John"伯复。第二個字段是 friendshipStatus慨代,其索引值是 6,找到值為 2啸如, 表示 NotFriend侍匙。第三個字段是 spouse,也一個 Person 對象叮雳,索引值是 12想暗,指向的是此對象的支點(diǎn)位置。第四個字段是一個數(shù)組帘不,圖中表示的數(shù)組為空说莫,所以索引值是 0。
通過上面的解析寞焙,可以看出储狭,F(xiàn)latBuffers 通過自己分配和管理對象的存儲,使對象在內(nèi)存中就是線性結(jié)構(gòu)化的捣郊,直接可以把內(nèi)存內(nèi)容保存或者發(fā)送出去辽狈,加載“解析”數(shù)據(jù)只需要把 byte 數(shù)組加載到內(nèi)存中即可,不需要任何解析呛牲,也不產(chǎn)生任何中間變量刮萌。
它與具體的機(jī)器或者運(yùn)行環(huán)境無關(guān),例如在 Java 中娘扩,對象內(nèi)的內(nèi)存不依賴 Java 虛擬機(jī)的堆內(nèi)存分配策略實(shí)現(xiàn)着茸,所以也是跨平臺的。
1琐旁、數(shù)據(jù)的序列化和反序列化
服務(wù)器對象Object----流--->客戶端Object對象
- 序列化: Serializable/Parcelable
- 時間:1ms * 10 * 50 * 20 = 10000ms
- 性能:內(nèi)存的浪費(fèi)和CPU計(jì)算時間的占用涮阔。
- 格式:json/xml(json序列化的工具GSON/fastjson)
2、FlatBuffer的優(yōu)點(diǎn)
FlatBuffer 相對于其他序列化技術(shù)旋膳,例如 XML澎语,JSON,Protocol Buffers 等验懊,有哪些優(yōu)勢呢擅羞?官方文檔的說法如下:
直接讀取序列化數(shù)據(jù),而不需要解析(Parsing)或者解包(Unpacking):FlatBuffer 把數(shù)據(jù)層級結(jié)構(gòu)保存在一個扁平化的二進(jìn)制緩存(一維數(shù)組)中义图,同時能夠保持直接獲取里面的結(jié)構(gòu)化數(shù)據(jù)减俏,而不需要解析,并且還能保證數(shù)據(jù)結(jié)構(gòu)變化的前后向兼容碱工。
高效的內(nèi)存使用和速度:FlatBuffer 使用過程中娃承,不需要額外的內(nèi)存奏夫,幾乎接近原始數(shù)據(jù)在內(nèi)存中的大小。
靈活:數(shù)據(jù)能夠前后向兼容历筝,并且能夠靈活控制你的數(shù)據(jù)結(jié)構(gòu)酗昼。
很少的代碼侵入性:使用少量的自動生成的代碼即可實(shí)現(xiàn)。
強(qiáng)數(shù)據(jù)類性梳猪,易于使用麻削,跨平臺,幾乎語言無關(guān)春弥。
3呛哟、 FlatBuffers 的缺點(diǎn):
- FlatBuffers 需要生成代碼,對代碼有侵入性匿沛;
- 數(shù)據(jù)序列化沒有可讀性扫责,不方便 Debug;
- 構(gòu)建 FlatBuffers 對象比較麻煩逃呼,不直觀鳖孤,特別是如果對象比較復(fù)雜情況下需要寫大段的代碼;
- 數(shù)據(jù)的所有內(nèi)容需要使用 Schema 嚴(yán)格定義蜘渣,靈活性不如 JSON淌铐。
所以肺然,在什么情況下選擇使用 FlatBuffers 呢蔫缸?個人感覺需要滿足以下幾點(diǎn):
- 項(xiàng)目中有大量數(shù)據(jù)傳輸和解析,使用 JSON 成為了性能瓶頸际起;
- 穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)定義拾碌。
4、FlatBuffer的使用
FlatBuffer:基于二進(jìn)制的文件街望。
json:基于字符串的
- 獲取flatbuffers代碼
首先校翔,我們需要得到 flatc,這個需要從源碼編輯得到灾前。從GitHub 上
Clone 代碼
$ git clone https://github.com/google/flatbuffers
- 編寫Schema
要使用 FlatBuffers 的 IDL 定義好數(shù)據(jù)結(jié)構(gòu) Schema防症,編寫 Schema 的詳細(xì)文檔。其語法和 C 語言類似哎甲,比較容易上手蔫敲。我們這里引用一個簡單的例子2,假設(shè)數(shù)據(jù)結(jié)構(gòu)如下:
class Person {
String name;
int friendshipStatus;
Person spouse;
List<Person>friends;
}
編寫成 Schema 如下炭玫,文件名為 Person.fbs:
namespace com.race604.fbs;
enum FriendshipStatus: int {Friend = 1, NotFriend}
table Person {
name: string;
friendshipStatus: FriendshipStatus = Friend;
spouse: Person;
friends: [Person];
}
root_type Person;
然后奈嘿,使用 flatc 可以把 Schema 編譯成多種編程語言,我們僅僅討論 Android 平臺吞加,所以把 Schema 編譯成 Java裙犹,找到flatc.exe執(zhí)行命令如下:
$ ./flatc –j -b Person.fbs
描述文件
namespace com.dn.ricky.performance.flatbuffer.lsn13_flatbuffer;
table Items {
ItemId : long;
timestemp : int;
basic:[Basic];
}
table Basic{
id:int;
name:string;
email:int;
code:long;
isVip:bool;
count:int;
carList:[Car];
}
table Car{
id:long;
number:long;
describle:string;
}
root_type Items;
public void serialize(View v){
//==================序列化========================
FlatBufferBuilder builder = new FlatBufferBuilder();
int id1 = builder.createString("蘭博基尼");
//準(zhǔn)備Car對象
int car1 = Car.createCar(builder,10001L,88888L,id1);
int id2 = builder.createString("奧迪A8");
//準(zhǔn)備Car對象
int car2 = Car.createCar(builder,10001L,88888L,id2);
int id3 = builder.createString("奧迪A9");
//準(zhǔn)備Car對象
int car3 = Car.createCar(builder,10001L,88888L,id3);
int[] cars = new int[3];
cars[0]= car1;
cars[1] = car2;
cars[2] = car3;
//創(chuàng)建Basic對象里面的Car集合
int carList = Basic.createCarListVector(builder,cars);
int name = builder.createString("jack");
int email = builder.createString("jack@qq.com");
int basic = Basic.createBasic(builder,10,name,email,100L,true,100,carList);
int basicOffset = Items.createBasicVector(builder,new int[]{basic});
/**
* table Items {
ItemId : long;
timestemp : int;
basic:[Basic];
}
*/
Items.startItems(builder);
Items.addItemId(builder,1000L);
Items.addTimestemp(builder,2016);
Items.addBasic(builder,basicOffset);
int rootItems = Items.endItems(builder);
Items.finishItemsBuffer(builder,rootItems);
//============保存數(shù)據(jù)到文件=================
File sdcard = Environment.getExternalStorageDirectory();
//保存的路徑
File file = new File(sdcard,"Items.txt");
if(file.exists()){
file.delete();
}
ByteBuffer data = builder.dataBuffer();
FileOutputStream out = null;
FileChannel channel = null;
try {
out = new FileOutputStream(file);
channel = out.getChannel();
while(data.hasRemaining()){
channel.write(data);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(out!=null){
out.close();
}
if(channel!=null){
channel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//===================反序列化=============================
FileInputStream fis = null;
FileChannel readChannel = null;
try {
fis = new FileInputStream(file);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
readChannel = fis.getChannel();
int readBytes = 0;
while ((readBytes=readChannel.read(byteBuffer))!=-1){
System.out.println("讀取數(shù)據(jù)個數(shù):"+readBytes);
}
//把指針回到最初的狀態(tài)尽狠,準(zhǔn)備從byteBuffer當(dāng)中讀取數(shù)據(jù)
byteBuffer.flip();
//解析出二進(jìn)制為Items對象。
Items items = Items.getRootAsItems(byteBuffer);
//讀取數(shù)據(jù)測試看看是否跟保存的一致
Log.i(TAG,"items.id:"+items.ItemId());
Log.i(TAG,"items.timestemp:"+items.timestemp());
Basic basic2 = items.basic(0);
Log.i(TAG,"basic2.name:"+basic2.name());
Log.i(TAG,"basic2.email:"+basic2.email());
//carList
int length = basic2.carListLength();
for (int i=0;i<length; i++){
Car car = basic2.carList(i);
Log.i(TAG,"car.number:"+car.number());
Log.i(TAG,"car.describle:"+car.describle());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(readChannel!=null){
readChannel.close();
}
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}