FlatBuffers简介
FlatBuffers是一个开源的、跨平台的、高效的、提供了C++/Java接口的序列化工具库。它是Google专门为游戏开发或其他性能敏感的应用程序需求而创建。尤其更适用于移动平台,这些平台上内存大小及带宽相比桌面系统都是受限的,而应用程序比如游戏又有更高的性能要求。它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而不需要任何解析开销。
特点
- 对序列化数据的访问不需要打包和拆包——它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而没有任何解析开销;
- 内存效率和速度——访问数据时的唯一内存需求就是缓冲区,不需要额外的内存分配。 这里可查看详细的 基准测试;
- 扩展性、灵活性——它支持的可选字段意味着不仅能获得很好的前向/后向兼容性(对于长生命周期的游戏来说尤其重要,因为不需要每个新版本都更新所有数据);
- 最小代码依赖——仅仅需要自动生成的少量代码和一个单一的头文件依赖,很容易集成到现有系统中。再次,看基准部分细节;
- 强类型设计——尽可能使错误出现在编译期,而不是等到运行期才手动检查和修正;
- 使用简单——生成的C++代码提供了简单的访问和构造接口;而且如果需要,通过一个可选功能可以用来在运行时高效解析Schema和类JSON格式的文本;
- 跨平台——支持C++11、Java,而不需要任何依赖库;在最新的gcc、clang、vs2010等编译器上工作良好;
存储结构
参考FaceBook的文档,假设我们有一个Person类定义如下:
假设有一个叫John的人,那么此Person对象在FlatBuffer中物理存储结构简化图如下,
1、每一个存储在FlatBuffer中的对象,被分割成2部分:中心点(Pivot Point)左边的元数据(vtable)和中心点右边的真实数据。
2、每个字段都对应vtable中的一个槽(slot),它存储了那个字段的真实数据的偏移量。例如,John的vtable的第一个槽的值是1,表明John的名字被存储在John的中心点右边的一个字节的位置上。
3、如果一个字段是一个对象,那么它在vtable中的偏移量会指向子对象的中心点。比如,John的vtable中第三个槽指向Mary的中心点。
4、要表明某个字段现在没有数据,可以在vtable对应的槽中使用偏移量0来标注。
Schema语言简介
Schema语言(即IDL)的语法与C语言家族、AIDL很类似。
Table
Tables是在FlatBuffers中定义对象的主要方式,如下图所示,每一个字段都是可选的,可配置默认值(如果忽略的话,默认是0/NULL)。
Structs
Structs与Table类似,不同点如下:
1.无默认值,且字段不能增加或被废弃(deprecated)
2.Struct只能包含标量或者其他struct
如果非常确定数据结构不会发生任何变化,那么就可以使用struct。Struct使用的内存比Table少,并且读取时比Table更快(它们通常被以in-line的方式存储在它们的父对象中,并且不适用virtual table)
Union、Enum功能与C语言类似
版本兼容
FlatBuffers具备良好的向前/向后兼容性,需要遵守以下规则:
1.如果要在schema中添加新字段,只能在table的末尾进行添加
2.如果不再使用某些字段了,不能从schema中删除它们;可以选择不再把它们写入到你的数据中,或者你通过deprecated来声明该字段不再使用,不过这样会使编译器不在产生该字段的代码,故存在一定的代码破坏性。
假设服务器端更新了协议,新增了一个字段,而客户端并未更新,那么当客户端收到服务器的数据时,新增的字段对客户端而言等同于没有增加,客户端不会解析新增的字段,保证了向前兼容性。
如果服务器协议回滚到一个低版本,那么当客户端收到服务器的数据进行解析时,客户端相较于服务端的新增字段,将会采用默认值,保证了向后兼容性。
更多版本控制细节,请参考Schemas and version control
支持的数据类型
内建标量:
内建非标量类型:
1.Vector ,其他数据类型的Vector (矢量),不支持内嵌
2.string,只能存储UTF-8或者7-bit ASCII。如果需要存储其他编码的文本,或者通用二进制数据,请使用vector([byte]或者[ubyte])
3.References,对其他table、struct、enum或者union的引用
更多关于编写Schema的信息,请参考Writing Schema
FlatBuffer使用步骤
1.编写Schema文件
2.使用FlatBuffers编译器生成java bean 文件,执行命令:
flatc --java samples/monster.fbs
编译器下载地址
3.编译FlatBuffers的java库,下载flatbuffers源码,进入目录执行mvn package,选择Target目录的完整jar包(第一个)
4.在Android工程中引入FlatBuffers的java库及编译器生成的java bean文件。
5.使用FlatBufferBuilder构造一个ByteBuffer(序列化对象),保存或者发送该Buffer。
6.读取该Buffer,通过getRootAsXXX可反序列化得到其代表的对象。
关于FlatBuffers可读性差的问题,可通过执行命令:
flatc --json person.fbs --raw-binary -- flat.bin
直接将二进制文件转换为json格式的文件,解决可读性差的问题
VS Java Serializable
对比测试序列化与反序列化如下对象1000次所需要的执行时间,测试结果如下:
与传统的Java序列化相比,FlatBuffers在序列化及反序列化的速度上极具优势,在存储空间的占用上也具备一定的优势,节约近乎二倍的存储空间。测试Demo的对象比较简单,当序列化对象更为复杂的时候,FlatBuffers在解析速度与空间占用的优势将更为明显。
Demo下载地址
VS json
与json相比,flatbuffers具备以下优点:
1.解析速度快到飞起(json还要走一次java序列化,速度比Java序列化还慢)
2.节约空间
3.反序列化的时候无内存抖动,就一个ByteBuffer,内存占用平稳,json在反序列化的时候会产生大量临时对象,造成内存抖动。