在服务端的开发过程当中,咱们常常须要完成 复杂数据结构 <–> 二进制数据 之间的序列化、反序列化操做。ios
与易于阅读的Json相比,Google Protocol Buffers是一个不错的选择。然而,其速度依然比较慢。去年,Google又开源了推出了一款序列化利器:Google FlatBuffers。本文将简介其用法,c++
一、为何要用Google FlatBuffersgit
我就不用复杂的文字描述了,一份官方Benchmark数据就足以说明问题:github
能够看到,与Protocol Buffers相比,尽管FlatBuffers在空间使用上不具备优点,可是反序列化上的性能很是彪悍!json
为何这么高效呢,援引官方的文档:数组
对序列化数据的访问不须要打包和拆包——它将序列化数据存储在缓存中,这些数据既能够存储在文件中,又能够经过网络原样传输,而没有任何解析开销;(这是最主要的缘由,ProtoBuffer、JSON等均须要拆包和解包)缓存
内存效率和速度——访问数据时的惟一内存需求就是缓冲区,不须要额外的内存分配。 这里可查看详细的基准测试;网络
扩展性、灵活性——它支持的可选字段意味着不只能得到很好的前向/后向兼容性(对于长生命周期的游戏来讲尤为重要,由于不须要每一个新版本都更新全部数据);数据结构
最小代码依赖——仅仅须要自动生成的少许代码和一个单一的头文件依赖,很容易集成到现有系统中。再次,看基准部分细节;函数
强类型设计——尽量使错误出如今编译期,而不是等到运行期才手动检查和修正;
使用简单——生成的C++代码提供了简单的访问和构造接口;并且若是须要,经过一个可选功能能够用来在运行时高效解析Schema和类JSON格式的文本;
跨平台——支持C++十一、Java,而不须要任何依赖库;在最新的gcc、clang、vs2010等编译器上工做良好;
二、编译&安装
能够在github上找到最新的release版本:https://github.com/google/flatbuffers/releases
wget https://github.com/google/flatbuffers/archive/v1.0.3.zip unzip ./v1.0.3.zip cd flatbuffers-1.0.3/ cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/coder4/soft/flatbuffers -G "Unix Makefiles" make && make install
1 2 3 4 5 |
wget https://github.com/google/flatbuffers/archive/v1.0.3.zip unzip ./v1.0.3.zip cd flatbuffers-1.0.3/ cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/coder4/soft/flatbuffers -G "Unix Makefiles" make && make install |
编译完毕的库和include就在中了。
和protobuffer相似,咱们本身开发时候并不须要连接其余lib,只要include和生成的代码就能够了。
最有用的是bin/flatc,这个是编译schema、生成代码的程序。
三、编写自定义Schema
只有schema肯定,才能保证序列化、反序列化的高性能(由于会生成裸代码,比json等动态执行的要高效不少)
咱们构造一个以下的Schema文件 test.fb
namespace TestApp; struct KV { key: ulong; value: double; } table TestObj { id:ulong; name:string; flag:ubyte = 0; list:[ulong]; kv:KV; } root_type TestObj;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
namespace TestApp;
struct KV { key: ulong; value: double; }
table TestObj { id:ulong; name:string; flag:ubyte = 0; list:[ulong]; kv:KV; }
root_type TestObj; |
简单解释一下,FlatBuffer,支持的数据结构有:基本类型和复杂类型。
基本类型:
8 bit: byte ubyte bool
16 bit: short ushort
32 bit: int uint float
64 bit: long ulong double
复杂类型:
数组 (用中括号表示 [type]
). 不支持嵌套数组,能够用table实现
字符串 string
, 支持 UTF-8 或者 7-bit ASCII. 对于其余编码能够用数组 [byte]或者[ubyte]表示。
Struct 只支持基本类型或者嵌套Struct
Table 相似Struct,可是能够支持任何类型。
看完这些,你们应该就很清楚上面的fb是怎么生成的啦。
KV是一个Struct,有2个名为key和value的变量。
TestObj是一个Table,包含了KV的成员、list数组、flag的uint8(初始值0)、以及uint64的id。
最后定义了根入口是TestObj,这句必定要有,不然没法反序列化。
四、编译Schema
执行:
./bin/flatc -c -b ./test.fb
1 |
./bin/flatc -c -b ./test.fb |
会生成一个.h文件:
test_generated.h
五、序列化、反序列化
#include "test_generated.h" #include <vector> #include <iostream> using namespace std; using namespace TestApp; int main() { flatbuffers::FlatBufferBuilder builder; /////////// Serialize ////////// // Create list std::vector<uint64_t> vec; for(size_t i=0;i<10;i++) { vec.push_back(i); } // Create flat buffer inner type auto id = 123; auto name = builder.CreateString("name"); auto list = builder.CreateVector(vec); // vector auto flag = 1; auto kv = KV(1, 1.0); // struct // table auto mloc = CreateTestObj(builder, id, name, flag, list, &kv); builder.Finish(mloc); char* ptr = (char*)builder.GetBufferPointer(); uint64_t size = builder.GetSize(); ////////// Deserialize ////////// auto obj = GetTestObj((uint8_t*)ptr); cout << obj->id() << endl; cout << obj->name()->c_str() << endl; cout << obj->flag() << endl; for(size_t i=0;i<obj->list()->size();i++) { cout << obj->list()->Get(i) << endl; } // can use assign to std::vector for speed up // vec.reserve(obj->list()->size()); // vec.assign(obj->list()->begin(), obj->list()->end()); cout << obj->kv()->key() << endl; cout << obj->kv()->value() << endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include "test_generated.h" #include <vector> #include <iostream>
using namespace std; using namespace TestApp;
int main() { flatbuffers::FlatBufferBuilder builder;
/////////// Serialize ////////// // Create list std::vector<uint64_t> vec; for(size_t i=0;i<10;i++) { vec.push_back(i); } // Create flat buffer inner type auto id = 123; auto name = builder.CreateString("name"); auto list = builder.CreateVector(vec); // vector auto flag = 1; auto kv = KV(1, 1.0); // struct // table auto mloc = CreateTestObj(builder, id, name, flag, list, &kv); builder.Finish(mloc);
char* ptr = (char*)builder.GetBufferPointer(); uint64_t size = builder.GetSize();
////////// Deserialize ////////// auto obj = GetTestObj((uint8_t*)ptr);
cout << obj->id() << endl; cout << obj->name()->c_str() << endl; cout << obj->flag() << endl; for(size_t i=0;i<obj->list()->size();i++) { cout << obj->list()->Get(i) << endl; }
// can use assign to std::vector for speed up // vec.reserve(obj->list()->size()); // vec.assign(obj->list()->begin(), obj->list()->end());
cout << obj->kv()->key() << endl; cout << obj->kv()->value() << endl;
} |
因为FlatBuffers使用了c++0x的特性,因此编译必须使用支持c++0x的版本,例如
g++ -std=c++0x ./test.cpp -I ./include/
1 |
g++ -std=c++0x ./test.cpp -I ./include/ |
对代码说明以下:
因为FlatBuffer中的类型小复杂,且官方也没有给出明确的例子,因此我就偷懒用了auto特性。处女座请自行参阅源代码。
基础类型直接赋值,符合类型须要用FlatBufferBuilder.CreateXXX,例如String和Vector
Struct类型,直接构造
Table类型,用CreateXXX,其中XXX为定义的类型,这个在生成代码的.h中
序列化时候,能够直接从Builder取出指针和length,而后就能够塞入string啦~
反序列化的时候,注意全部成员都须要用函数()而不是直接使用成员名。
小结一下,生成的代码还真是小乱,用法五花八门,为了性能就忍忍吧,用习惯就行了。
最后说一句,FlatBuffer还支持其余更为高级的用法,例如直接反序列化为Json/从Json序列化,可是性能比较慢,你们慢慢探索吧。