數(shù)據(jù)傳輸優(yōu)化之FlatBuffers

下面介紹三個數(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的一張圖如下:

Paste_Image.png

可見,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):

  1. 項(xiàng)目中有大量數(shù)據(jù)傳輸和解析,使用 JSON 成為了性能瓶頸际起;
  1. 穩(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();
            }
        }
    }

json傳輸?shù)倪^程解析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叶圃,一起剝皮案震驚了整個濱河市袄膏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掺冠,老刑警劉巖哩陕,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赫舒,居然都是意外死亡悍及,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門接癌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來心赶,“玉大人,你說我怎么就攤上這事缺猛∮Ы校” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵荔燎,是天一觀的道長耻姥。 經(jīng)常有香客問我,道長有咨,這世上最難降的妖魔是什么琐簇? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮座享,結(jié)果婚禮上婉商,老公的妹妹穿的比我還像新娘。我一直安慰自己渣叛,他們只是感情好丈秩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淳衙,像睡著了一般蘑秽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箫攀,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天肠牲,我揣著相機(jī)與錄音,去河邊找鬼匠童。 笑死埂材,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汤求。 我是一名探鬼主播俏险,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼严拒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了竖独?” 一聲冷哼從身側(cè)響起裤唠,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎莹痢,沒想到半個月后种蘸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竞膳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年航瞭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦辟。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡刊侯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锉走,到底是詐尸還是另有隱情滨彻,我是刑警寧澤敲才,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布憨颠,位于F島的核電站障簿,受9級特大地震影響戴而,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜懊渡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一蝗肪、第九天 我趴在偏房一處隱蔽的房頂上張望别瞭。 院中可真熱鬧懂算,春花似錦只冻、人聲如沸庇麦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽山橄。三九已至垮媒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間航棱,已是汗流浹背睡雇。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饮醇,地道東北人它抱。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像朴艰,于是被迫代替她去往敵國和親观蓄。 傳聞我的和親對象是個殘疾皇子混移,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容