Flatbuffers使用解析
在今日头条偶然看到一个技术分享视频,标题很唬人:json之后下一代数据交换格式(大致是这样),不明觉厉,赶紧打开观看,原来Flatbuffers是Google为游戏或者其他对性能要求很高的应用开发的一种数据交换格式。小小试用了下,掌握了基本用法,特此记录,以分享和备忘。
相比json,存储4M数据的json,转化为flatbuffers只需要2M左右,解析时,json花费800多ms,flatbuffer每次都在10ms以内,json解析时应用内存占用飙升,flatbuffer几乎不变。 各种数据交换格式性能对比介绍及flatbuffer简介 。
使用步骤:
一、下载依赖、编译器
java使用时,需用到一下四个类:见下图:
编译器即是faltc:用于从数据结构schema文件生成相应类。好比json生成bean文件。可以直接下载exe文件,也可以下载源码自行生成相应程序,Flatbuffers官方API有详细说明如何生成编译器。
二、编写schema文件
示例json文件test.json如下:
{
"items": [{
"id": 1001,
"name": "张三",
"code": 1222,
"carList": [{
"id": 10001,
"number": 123456321,
"describle": "这是张三第一辆车"
}]
}, {
"id": 1002,
"name": "李四",
"code": 1123,
"carList": [{
"id": 10001,
"number": 123456001,
"describle": "这是李四第一辆车"
}, {
"id": 10002,
"number": 123456002,
"describle": "这是李四第二辆车"
}, {
"id": 10003,
"number": 123456003,
"describle": "这是李四第三辆车"
}]
}],
"stateid":404,
"time":20161127
}
编写对应的schema文件test.fb如下:
namespace Fltest;
table Car{
id:int;
number:long;
describle:string;
}
table Person{
id:int;
name:string;
code:long;
isVip:bool;
carList:[Car];
}
table RootType{
items:[Person];
stateid:int;
time:long;
}
root_type RootType;
schema文件说明:
- namespace 命名空间就是使用编译器后生成的文件夹名,生成的类文件都放在此文件下
- 后缀格式fb都行
- 字段后面可跟默认值,如:id:int=0;
- 支持基本类型以及内置类型string。unions型使用字段table标识,table 后就是生成的类名。
- 必须有根类型使用root_type标明根类型。
- 详情参考官方API:
https://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html
三、编译生成类文件
打开命令窗口定位到flatc所在文件夹,使用命令
flatc -j -b ./test.fb
即可生成类文件。
fltest即是生成的类文件夹里面有如下3个类。
也可以使用命令:
flatc -j -b ./test.fb ./test.json
由json文件直接生成flatbuffers的字节码文件即二进制test.bin文件。也会自动生成类文件。
四、序列化数据
把生成的类文件以及四个java依赖类均导入项目中,即可开始使用。
序列化:
//序列化数据作用类似于json中的构建JSONobject对象。
private void initflat() {
FlatBufferBuilder builder = new FlatBufferBuilder();
//从里往外构建,最里层是String;
//每个对象存的都是偏移量类似于指针
//命名_id指偏移量offset
int describle_id = builder.createString("这是张三的第一辆车");
int car_id = Car.createCar(builder, 10001, 123456321L, describle_id);
int[] cars = {car_id};
int cars_id = Person.createCarListVector(builder, cars);
int name_id = builder.createString("张三");
int person1_id = Person.createPerson(builder, 1001, name_id, 1222L, true, cars_id);
//items中person1建立完成。
int describle1_id = builder.createString("这是李四的第一辆车");
int car1_id = Car.createCar(builder, 10001, 123456001L, describle1_id);
int describle2_id = builder.createString("这是李四的第二辆车");
int car2_id = Car.createCar(builder, 10002, 123456002L, describle2_id);
int describle3_id = builder.createString("这是李四的第三辆车");
int car3_id = Car.createCar(builder, 10003, 123456003L, describle3_id);
int[] cars2 = {car1_id, car2_id, car3_id};
int cars2_id = Person.createCarListVector(builder, cars2);
int name2_id = builder.createString("李四");
int person2_id = Person.createPerson(builder, 1002, name2_id, 1223L, false, cars2_id);
//items中person2建立完成
int[] persons = {person1_id, person2_id};
int persons_id = RootType.createItemsVector(builder, persons);
int roottype_id = RootType.createRootType(builder, persons_id, 404, 20161127L);
RootType.finishRootTypeBuffer(builder, roottype_id);
//根类型RootType构建完成。得到builder可获取到bytebuffer然后即可存入文件,也可直接发送到网络。
//此处存入文件中。
storageFlat(builder);
}
//存储数据到文件。
private void storageFlat(FlatBufferBuilder builder) {
ByteBuffer data = builder.dataBuffer();//得到底层存储二进制数据的ByteBuffer
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard, "Flattest.txt");
if (file.exists()) {
file.delete();
}
FileOutputStream out = null;
FileChannel channel = null;
try {
out = new FileOutputStream(file);
channel = out.getChannel();
while (data.hasRemaining()) {
try {
channel.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (null != channel) {
channel.close();
}
if (null != out) {
out.close();
}
} catch (Exception e2) {
}
}
}
序列化说明:
- 从最内层对象往外构建,flatbuffers采用直接在内存中存取数据,基本数据类型可在相应类的create构建方法中直接当参数传入,类对象则需要先获取其存储偏移地址,然后把该地址作为create参数传入。所以出现了一堆int型数据均是各个类对象的偏移地址。
- list集合类对象,定义一个整形数组,整形数组中放各个对象的偏移地址。在把数据作为参数传给create构造器。
- 理解flatbuffers的内存模型很重要,这篇文章介绍的很详细:
http://blog.youkuaiyun.com/yxz329130952/article/details/50880191
五、反序列化(解析)
由于使用的是文件存储,所以要从文件解析flatbuffers数据。
//反序列化flatbuffer 即解析
private void getFlat() {
File file = new File(Environment.getExternalStorageDirectory(), "Flattest.txt");
FileInputStream fin = null;
FileChannel readChannel = null;
try {
fin = new FileInputStream(file);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
readChannel = fin.getChannel();
int readbytes = 0;
while ((readbytes = readChannel.read(byteBuffer)) != -1) {
System.out.printf("读到:" + readbytes + "个数据");
}
byteBuffer.flip();//positon回绕,准备从bytebuffer中读取数据
RootType rootType = RootType.getRootAsRootType(byteBuffer);
//测试读取的数据与写入的数据是否一致
StringBuilder sb = new StringBuilder();
sb.append("RootType--->" + "id:" + rootType.stateid() + " time:" + rootType.time() + "\n");
Person person1 = rootType.items(0);
Person person2 = rootType.items(1);
Car car = person1.carList(0);
Car car1 = person2.carList(0);
Car car2 = person2.carList(1);
Car car3 = person2.carList(2);
sb.append("person1-->" + "id:" + person1.id() + " name:" + person1.name() + " code:" + person1.code()
+ " isVip:" + person1.isVip() + "\n");
sb.append("car->" + "id:" + car.id() + " number:" + car.number() + " describle:" + car.describle() + "\n");
sb.append("Person2-->" + "id:" + person2.id() + " name:" + person2.name() + " code:" + person2.code()
+ " isVip:" + person2.isVip() + "\n");
sb.append("car1->" + "id:" + car1.id() + " number:" + car1.number() + " describle:" + car1.describle() + "\n");
sb.append("car2->" + "id:" + car2.id() + " number:" + car2.number() + " describle:" + car2.describle() + "\n");
sb.append("car3->" + "id:" + car3.id() + " number:" + car3.number() + " describle:" + car3.describle() + "\n");
tvflat.setText(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != readChannel) {
readChannel.close();
}
if (null != fin) {
fin.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
解析结果如下图:
完整代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btflat;
private TextView tvflat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btflat = (Button) findViewById(R.id.bt_flat);
tvflat = (TextView) findViewById(R.id.tv_flat);
btflat.setOnClickListener(this);
initflat();
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.bt_flat:
getFlat();
break;
default:
break;
}
}
//省略了3个方法前面已贴出。initflat()、storageFlat()、getFlat()。**
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="100dp"
android:layout_height="40dp"
android:text="解析FB"
android:layout_marginBottom="77dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:id="@+id/bt_flat" />
<TextView
android:text="TextView"
android:layout_width="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:id="@+id/tv_flat"
android:layout_height="350dp"
android:background="@android:color/holo_blue_dark"
android:textColor="@android:color/black"
android:textSize="16sp" />
</RelativeLayout>
别忘了加上读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
总结
1、flatbuffers是谷歌2014年推出的,之所以到现在国内都很少有讨论它的,个人认为有以下两点:
- json的性能虽然很低但其可读性强,数据结构更改便捷。性能方面,数据量小的时候,性能影响并不是很大,flatbuffers的提升不太明显。
- flatbuffers适合数据量交互很大,或者很频繁的应用来使用。而其不便之处就在于它存储的是字节,基本无法直观读取,当然这也有好处,别人截获数据,没有对应的schema文件几乎无法获得任何信息。
2、个人水平有限所以很少涉及它的原理等深层次的东西,只是介绍了其基本使用方法,更多的是给自己留个底吧。当然能帮助到他人,更是乐意至极,毕竟互联网分享精神还是要发扬的,从别人那获取那么多知识,总要回馈的。
3、这是我看的那个视频,从爱奇艺搜到了链接在此。http://www.iqiyi.com/w_19rum2zoc1.html