mac环境上编辑器flatc生成
参考http://blog.csdn.net/yxz329130952/article/details/50706369
- 下载flatbuffers的源码,解压
- homebrew下载安装cmake
- cd到flatbuffers的源码文件夹
- 开始编译flatc
cd flatbuffers/
cmake -G "Unix Makefiles"
make
指定生成的对象为Unix makefile,以便cmake根据CMakeLists.txt文件生成对应的makefile文件。(在当前目录下可以看到生成的makefile文件,直接执行make命令即可得到flatc。
Java文件编译
./flatc -j -o ~/workspace/testfb repos_schema.fbs
-j表示生成java文件,–o 表示将文件编译到指定目录~/workspace/testfb下,schema文件的位置放在命令的最后。
命令详细用法可见官方文档
schema语法
可见官方文档
源码剖析
重点:扁平二进制的存储方式。
- 使用bytebuffer操作byte数据
- 逆向存储,将数据概要信息放在整个buffer的头部。这也回答了作者文中的疑问,为什么要费劲的反向存储,即从buffer的尾部开始写数据。
- offset的概念,每个数据距离buffer尾部的长度。
- vtable中存储的是某个数据相对于vtable的偏移。寻址时只要将当前pos+偏移就得到真实数据信息摘要所在位置。因此,根据roottype的数据Vtable,就可以找到一切。
androidstudio中的使用示例
- gradle添加依赖
compile 'com.github.davidmoten:flatbuffers-java:1.4.0.1'
- 定义schema
我们以这个数据样式(Json)为例
{
"error_no":0,
"message":"",
"result":{
"data":[
{
"datatype":1,
"itemdata":
{//共有字段45个
"sname":"\u5fae\u533b",
"packageid":"330611",
…
"tabs":[
{
"type":1,
"f":"abc"
},
…
]
}
},
…
],
"hasNextPage":true,
"dirtag":"soft"
}
}
- 编写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;
就以上示例简单讲解一下:
命名空间
namespace决定了编译生成文件的存放位置,类似c的写法,同样,不同的schema文件可以互相引用,通过include的方式
table-表
表是在FlatBuffers中定义对象的主要方式,它由一个名称和一个字段列表组成。每个字段都有一个名称,一个类型和可选的默认值(如果省略,则默认为0/ NULL)。
每个字段都是可选的,因此,可以灵活地添加字段,而不必担心数据膨胀。此设计也是FlatBuffer的前后兼容机制。
注意:
- 只能在表定义的末尾添加新字段。较旧的数据仍将正确读取,并在读取时给予默认值。
- 不能从模式中删除不再使用的字段,可以用deprecated标记它们,这将阻止在生成class中生成访问器,以此来强制不再使用该字段。
根类型
root_type必须指定,序列化数据的根结构,解析开始的位置。
-
flatc编译后生成文件目录如下
将编译生成的文件导入工程即可开始编码了。
序列化示例
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,每个Data下有多个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,多个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);
//将当前data数据的offset写入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);
//调用finish结束此次构造
builder.finish(responseOfset);
}
序列化后的数据写入文件,特容易踩坑!
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的数据写入方向是反向写入!!因此build结束时数据的开始位置为position标记所在位置,因此要指定write的开始为bb.position(),写入长度为bb.remaining(),否则会导致文件反序列化失败。
反序列化
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();
}
}
}
这里需要说明的是,序列化过程中每个table中的数据可以无序写入,而反序列化时也不必按序读出,因为扁平二进制的存储方式已经保证了读取方式寻址进行而不会出错。此外,反序列化的速度直接受读取数据多少的影响,由于反序列化不需要进行封包解析的过程,因此数据可以随意读取,而读取的数据个数越多所消耗的时间越长。这也是flatbuffers的妙处所在。
flatbuffers还有很多更有趣的用法等待探索。