mac環(huán)境上編輯器flatc生成
參考http://blog.csdn.net/yxz329130952/article/details/50706369
- 下載flatbuffers的源碼,解壓
- homebrew下載安裝cmake
- cd到flatbuffers的源碼文件夾
- 開(kāi)始編譯flatc
cd flatbuffers/
cmake -G "Unix Makefiles"
make
指定生成的對(duì)象為Unix makefile,以便cmake根據(jù)CMakeLists.txt文件生成對(duì)應(yīng)的makefile文件。(在當(dāng)前目錄下可以看到生成的makefile文件,直接執(zhí)行make命令即可得到flatc。
Java文件編譯
./flatc -j -o ~/workspace/testfb repos_schema.fbs
-j表示生成java文件,–o 表示將文件編譯到指定目錄~/workspace/testfb下,schema文件的位置放在命令的最后。
命令詳細(xì)用法可見(jiàn)官方文檔
schema語(yǔ)法
可見(jiàn)官方文檔
源碼剖析
參見(jiàn)android FlatBuffers剖析
重點(diǎn):扁平二進(jìn)制的存儲(chǔ)方式。
- 使用bytebuffer操作byte數(shù)據(jù)
- 逆向存儲(chǔ),將數(shù)據(jù)概要信息放在整個(gè)buffer的頭部。這也回答了作者文中的疑問(wèn),為什么要費(fèi)勁的反向存儲(chǔ),即從buffer的尾部開(kāi)始寫(xiě)數(shù)據(jù)。
- offset的概念,每個(gè)數(shù)據(jù)距離buffer尾部的長(zhǎng)度。
- vtable中存儲(chǔ)的是某個(gè)數(shù)據(jù)相對(duì)于vtable的偏移。尋址時(shí)只要將當(dāng)前pos+偏移就得到真實(shí)數(shù)據(jù)信息摘要所在位置。因此,根據(jù)roottype的數(shù)據(jù)Vtable,就可以找到一切。
androidstudio中的使用示例
- gradle添加依賴
compile 'com.github.davidmoten:flatbuffers-java:1.4.0.1'
- 定義schema
我們以這個(gè)數(shù)據(jù)樣式(Json)為例
{
"error_no":0,
"message":"",
"result":{
"data":[
{
"datatype":1,
"itemdata":
{//共有字段45個(gè)
"sname":"\u5fae\u533b",
"packageid":"330611",
…
"tabs":[
{
"type":1,
"f":"abc"
},
…
]
}
},
…
],
"hasNextPage":true,
"dirtag":"soft"
}
}
- 編寫(xiě)schema如下
namespace flats;
table TabFB {
type : int;
f : string;
}
table ItemDataFB {
sname : string;
packageid : string;
...
tabs : [TabFB];
}
table DataFB {
datatype : int;
itemdata : ItemDataFB;
}
table ResultFB {
data : [DataFB];
hasNextPage : bool;
dirtag : string;
}
table ResponseFB {
error_no : int;
message : string;
result : ResultFB;
}
root_type ResponseFB;
就以上示例簡(jiǎn)單講解一下:
命名空間
namespace決定了編譯生成文件的存放位置,類(lèi)似c的寫(xiě)法,同樣,不同的schema文件可以互相引用,通過(guò)include的方式
table-表
表是在FlatBuffers中定義對(duì)象的主要方式,它由一個(gè)名稱和一個(gè)字段列表組成。每個(gè)字段都有一個(gè)名稱,一個(gè)類(lèi)型和可選的默認(rèn)值(如果省略,則默認(rèn)為0/ NULL)。
每個(gè)字段都是可選的,因此,可以靈活地添加字段,而不必?fù)?dān)心數(shù)據(jù)膨脹。此設(shè)計(jì)也是FlatBuffer的前后兼容機(jī)制。
注意:
- 只能在表定義的末尾添加新字段。較舊的數(shù)據(jù)仍將正確讀取,并在讀取時(shí)給予默認(rèn)值。
- 不能從模式中刪除不再使用的字段,可以用deprecated標(biāo)記它們,這將阻止在生成class中生成訪問(wèn)器,以此來(lái)強(qiáng)制不再使用該字段。
根類(lèi)型
root_type必須指定,序列化數(shù)據(jù)的根結(jié)構(gòu),解析開(kāi)始的位置。
-
flatc編譯后生成文件目錄如下
編譯生成文件目錄.png 將編譯生成的文件導(dǎo)入工程即可開(kāi)始編碼了。
序列化示例
public static void writeResponseToFbFile(String fbfilepath, ResponseJson responseJson) {
FlatBufferBuilder builder = new FlatBufferBuilder(0);
int dataitemsize = responseJson.result.data.size();
int[] dataArr = new int[dataitemsize];
//data begin,每個(gè)Data下有多個(gè)itemdata
for (int i = 0; i < dataitemsize; i++) {
DataItemJson dataItemJson = responseJson.result.data.get(i);
ItemJson itemJson = dataItemJson.itemdata;
//itemdata begin
int sname = builder.createString(itemJson.sname);
int packageid = builder.createString(itemJson.packageid);
....
//tabs begin,多個(gè)tab
int tabnum = itemJson.tabs.size();
int[] tabsArr = new int[tabnum];
for (int j = 0; j < tabnum; j ++) {
//TabFB begin
TabsJson tabsJson = itemJson.tabs.get(j);
tabsArr[j] = TabFB.createTabFB(builder, tabsJson.type, builder.createString(tabsJson.f));
//TabFB end
}
int tabs = ItemDataFB.createTabsVector(builder,tabsArr);
//tabs end,返回tabs vtable所在位置的offset
ItemDataFB.startItemDataFB(builder);
ItemDataFB.addSname(builder,sname);
ItemDataFB.addPackageid(builder, packageid);
ItemDataFB.addTabs(builder,tabs);
int itemdataofset = ItemDataFB.endItemDataFB(builder);
//ItemDataFB end,返回itemdata的offset
//DataFb
int dataoffset = DataFB.createDataFB(builder, dataItemJson.datatype, itemdataofset);
//將當(dāng)前data數(shù)據(jù)的offset寫(xiě)入vtable
dataArr[i] = dataoffset;
}
//封裝result
int data = ResultFB.createDataVector(builder,dataArr);
int dirtag = builder.createString(responseJson.result.dirtag);
int resultOfset = ResultFB.createResultFB(builder,data,responseJson.result.hasNextPage,dirtag);
// response
int message = builder.createString(responseJson.message);
int responseOfset = ResponseFB.createResponseFB(builder,responseJson.error_no,message,resultOfset);
//調(diào)用finish結(jié)束此次構(gòu)造
builder.finish(responseOfset);
}
序列化后的數(shù)據(jù)寫(xiě)入文件,特容易踩坑!
ByteBuffer bb = builder.dataBuffer();
File file = new File(fbfilepath);
try {
FileOutputStream fo = new FileOutputStream(file);
fo.write(bb.array(),bb.position(),bb.remaining());
fo.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
這里要特別注意,由于flatbuffers的數(shù)據(jù)寫(xiě)入方向是反向?qū)懭?/strong>?。∫虼薭uild結(jié)束時(shí)數(shù)據(jù)的開(kāi)始位置為position標(biāo)記所在位置,因此要指定write的開(kāi)始為bb.position(),寫(xiě)入長(zhǎng)度為bb.remaining(),否則會(huì)導(dǎo)致文件反序列化失敗。
反序列化
public static void parse (byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
ResponseFB responsefb = ResponseFB.getRootAsResponseFB(bb);
int errorno = responsefb.errorNo();
responsefb.message();
ResultFB resultFB = responsefb.result();
resultFB.hasNextPage();
resultFB.dirtag();
int len = resultFB.dataLength();
for (int i = 0; i < len; i++) {
resultFB.data(i).datatype();
ItemDataFB item = resultFB.data(i).itemdata();
String s = item.sname();
item.packageid();
...
int tabslen = item.tabsLength();
for (int j = 0; j < tabslen; j++) {
TabFB tab = item.tabs(j);
tab.f();
tab.type();
}
}
}
這里需要說(shuō)明的是,序列化過(guò)程中每個(gè)table中的數(shù)據(jù)可以無(wú)序?qū)懭?,而反序列化時(shí)也不必按序讀出,因?yàn)楸馄蕉M(jìn)制的存儲(chǔ)方式已經(jīng)保證了讀取方式尋址進(jìn)行而不會(huì)出錯(cuò)。此外,反序列化的速度直接受讀取數(shù)據(jù)多少的影響,由于反序列化不需要進(jìn)行封包解析的過(guò)程,因此數(shù)據(jù)可以隨意讀取,而讀取的數(shù)據(jù)個(gè)數(shù)越多所消耗的時(shí)間越長(zhǎng)。這也是flatbuffers的妙處所在。
flatbuffers還有很多更有趣的用法等待探索。
