Flatbuffers

Flatbuffers使用解析

  在今日头条偶然看到一个技术分享视频,标题很唬人:json之后下一代数据交换格式(大致是这样),不明觉厉,赶紧打开观看,原来Flatbuffers是Google为游戏或者其他对性能要求很高的应用开发的一种数据交换格式。小小试用了下,掌握了基本用法,特此记录,以分享和备忘。
  
  相比json,存储4M数据的json,转化为flatbuffers只需要2M左右,解析时,json花费800多ms,flatbuffer每次都在10ms以内,json解析时应用内存占用飙升,flatbuffer几乎不变。 各种数据交换格式性能对比介绍及flatbuffer简介 。

使用步骤:

一、下载依赖、编译器

java使用时,需用到一下四个类:见下图:
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.csdn.net/yxz329130952/article/details/50880191

五、反序列化(解析)

由于使用的是文件存储,所以要从文件解析flatbuffers数据。

 //反序列化flatbuffer 即解析
//反序列化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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值