最近有项目用到了FlatBuffer,本文就以极简的方式介绍FlatBuffers的环境配置和使用方式。
作者使用MAC OSX操作系统,使用IDEA开发工具。可能读者环境与作者有所不同,但无论是什么环境,整体思路和步骤是相似的,希望本文能给读者有所帮助。
一、FlatBuffers简介
FlatBuffers为Google发布的一个跨平台,提供多种语言接口,注重性能和资源使用的序列化类库。目前该类库提供C++, C#, C, Go, Java, JavaScript, PHP, and Python语言接口。该序列化类库多用于移动端手游数据传输以及特定的对性能有较高要求的应用。
接下来我们将学习FlatBuffers环境搭建并且使用Java语言完成一次简单的序列化例子。
- 编译
flatc
工具 - 编写一个FlatBuffers的scheme文件
- 使用
flatc
工具编译scheme文件,生成对应语言的数据对象头文件/类 - 使用FlatBufferBuilder序列化对象
- 反序列化数据对象
二、编译flatc工具
FlatBuffers源码工程在CMake/文件夹下为我们提供了*.cmake文件,方便我们使用Cmake工具编译工程生成flatc
工具。
2.1 Cmake环境配置
Cmake官网下载制定平台的二进制安装包,笔者为MAC OSX平台所以直接下载dmg格式的安装文件,读者可根据自身平台下载相应的二进制安装包。当然读者也可以直接下载具体平台的源码进行编译。
下载安装完成后需要将cmake
命令加入PATH下,笔者使用zsh的shell,因此相关的配置文件在~/.zshrc文件下,如果读者使用不同shell,请在正确的位置配置。
export CMAKE_HOME=/Applications/CMake.app/Contents
export PATH=$CMAKE_HOME/bin:$PATH
其中/Applications/CMake.app/Contents
为Cmake的安装根目录,读者需根据具体安装位置进行修改。
然后运行如下命令将最新加载最新配置:
source .zshrc
重新启动终端输入如下命令
cmake --help
若看到如下信息则证明配置完成:
Usage
cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
......//省略更多的信息
2.2 下载FlatBuffers源码
从gitHub上下载项目源代码:
git clone git@github.com:google/flatbuffers.git
2.3 编译、安装flatc
进入flatbuffers项目根目录,输入如下命令:
cmake -G "Unix Makefiles"
稍等一会cmake就完成了MakeFile的生成,接下来运行:
make
开始编译,稍等一会编译成功后会在根目录下生成flatc
工具。
接下来我们使用
make install
命令,安装flatc
,该命令将flatc
工具拷贝到/usr/local/bin/目录下(环境配置不同可能有所不同),重新启动终端输入
flatc --version
命令会打印当前flatc的版本信息,笔者输出结果如下:
flatc version 1.6.0
至此,我们完成了flatc工具的编译和安装。
三、编写FlatBuffers的scheme文件
本文使用官网教程里面的例子,笔者进行了整理并加入自己的理解和说明。
// Example IDL file for our monster's schema.
namespace com.zeyuan.learning;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3; // Struct.
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte]; // Vector of scalars.
color:Color = Blue; // Enum.
weapons:[Weapon]; // Vector of tables.
equipped:Equipment; // Union.
}
table Weapon {
name:string;
damage:short;
}
root_type Monster;
我们对上述文件进行简要说明。整个文件配置方式偏向类C的方式第一行namespace定义了该scheme的命名空间,在JAVA环境下为包名。然后使用enum关键字定义类枚举类型Color,使用union定义共用体(C风格),struct定义了名为Vec3的结构体,table定义了名为Monster复合类结构,其中它包含定义的结构体、共用体、枚举等。最后root_type指明Monster类为根类型。当然这里我们只是很简单的讲解了scheme的含义,如果想具体了解语法知识可查看写一个scheme这篇官网文档。
四、编译scheme文件
将上节编写的scheme文件保存为monster.fbs文件,到该文件所在文件夹下,执行
flatc --java monster.fbs
将会生成Java语言的类文件定义,如果你想为别的语言生成相应的类文件可查看该文档。
五、代码示例
创建一个普通的java工程,你有两种方式引入FlatBuffers的相关语言的API:
- 将FlatBuffers源码下java文件夹内容拷贝到项目工程源码路径下;
- 或者 使用Maven或Gradle项目管理工具从Maven仓库下载Jar包。
笔者使用Gradle创建项目,因此在项目build.gradle文件内依赖关系配置如下:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
// https://mvnrepository.com/artifact/com.github.davidmoten/flatbuffers-java
compile 'com.github.davidmoten:flatbuffers-java:1.6.0.2'
}
工程测试代码如下:
public class SampleBinary {
public static void main(String[] args){
//使用FlatBufferBuilder 完成对象序列化
FlatBufferBuilder builder = new FlatBufferBuilder(1024);
//返回该String的偏移地址
int weaponOneName = builder.createString("Sword");
short weaponOneDamage = 3;
int weaponTwoName = builder.createString("Axe");
short weaponTwoDamage = 5;
// 使用createWeapon创建Weapon对象,并返回该对象的偏移地址
int sword = Weapon.createWeapon(builder, weaponOneName, weaponOneDamage);
int axe = Weapon.createWeapon(builder, weaponTwoName, weaponTwoDamage);
// Serialize a name for our monster, called "Orc".
int name = builder.createString("Orc");
// 创建一个Vector对象,并且返回它的偏移地址
byte[] treasure = {0, 1, 13, 12, 4, 5, 6, 7, 8, 9};
int inv = Monster.createInventoryVector(builder, treasure);
// Place the two weapons into an array, and pass it to the `createWeaponsVector()` method to
// create a FlatBuffer vector.
int[] weaps = new int[2];
weaps[0] = sword;
weaps[1] = axe;
// Pass the `weaps` array into the `createWeaponsVector()` method to create a FlatBuffer vector.
int weapons = Monster.createWeaponsVector(builder, weaps);
// startMonster声明开始创建Monster对象,使用endMonster声明完成Monster对象
Monster.startMonster(builder);
Monster.addPos(builder, Vec3.createVec3(builder, 1.0f, 2.0f, 3.0f));
Monster.addName(builder, name);
Monster.addColor(builder, Color.Red);
Monster.addHp(builder, (short)300);
Monster.addInventory(builder, inv);
Monster.addWeapons(builder, weapons);
Monster.addEquippedType(builder, Equipment.Weapon);
Monster.addEquipped(builder, axe);
int orc = Monster.endMonster(builder);
// 调用finish方法完成Monster对象
builder.finish(orc); // You could also call `Monster.finishMonsterBuffer(builder, orc);`.
// 生成二进制文件
byte[] buf = builder.sizedByteArray();
// 至此完成对象数据序列化
//模拟从获取到二进制数据 进行反序列化对象
ByteBuffer buffer = ByteBuffer.wrap(buf);
//根据该二进制数据列生成Monster对象
Monster monster = Monster.getRootAsMonster(buffer);
short hp = monster.hp();
System.out.println(hp);
short mana = monster.mana();
System.out.println(mana);
String resultName = monster.name();
System.out.println(resultName);
Vec3 pos = monster.pos();
float x = pos.x();
float y = pos.y();
float z = pos.z();
System.out.println("X: "+x+" Y: "+y+" Z: "+z);
int invLength = monster.inventoryLength();
int thirdItem = monster.inventory(2);
System.out.println(thirdItem);
int weaponsLength = monster.weaponsLength();
String secondWeaponName = monster.weapons(1).name();
short secondWeaponDamage = monster.weapons(1).damage();
System.out.println("weaponsLength: "+weaponsLength+" secondWeaponName: "+secondWeaponName+" secondWeaponDamage: "+secondWeaponDamage);
int unionType = monster.equippedType();
if (unionType == Equipment.Weapon) {
Weapon weapon = (Weapon)monster.equipped(new Weapon()); // Requires explicit cast
// to `Weapon`.
String weaponName = weapon.name(); // "Axe"
short weaponDamage = weapon.damage(); // 5
System.out.println("weaponName: "+weaponName+" weaponDamage: "+weaponDamage);
}
}
}
上述代码主要是使用FlatBufferBuilder完成对象序列化然后将序列化的二进制数据反序列化并打印出来。代码中对关键部分都增加了代码注释,这里就不再详细解释带吗细节了。
至此我们完成了FlatBuffers工具的入门,读者可以直接下载示例代码以便加深理解。