FlatBuffers Schema解析

FlatBuffers简介
FlatBuffers Schema解析
FlatBuffers序列化过程
FlatBuffers反序列化过程

本文对FlatBuffers的Schema进行解析,顺便贴出编译后的Java类型供参考。仍然使用官方Tutorial中的monster.fbs

namespace MyGame.Sample;

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;
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];
  color:Color = Blue;
  weapons:[Weapon];
  equipped:Equipment;
}

table Weapon {
  name:string;
  damage:short;
}

root_type Monster;

下面以Java版本逐行解释。

1. namespace关键字

namespace MyGame.Sample;
namespace定义命名空间,对应到Java中就是包名,这里的命名跟Java的命名习惯有点不一致。上面的语句使得生产的java类的包路径为
package MyGame.Sample。

2. enum关键字

enum Color:byte { Red = 0, Green, Blue = 2 }
定义枚举类。和常规的枚举类稍有不同的地方是可以定义类型(Java中是顺序是int类型),比如这里的Color是byte类型。enum字段只能新增,不能废弃,代码需要自己处理可能新增的类型。
enum关键字会生成一个Java类,如下:

public final class Color {
  private Color() { }
  public static final byte Red = 0;
  public static final byte Green = 1;
  public static final byte Blue = 2;

  public static final String[] names = { "Red", "Green", "Blue", };

  public static String name(int e) { return names[e]; }
}

3. union关键字

union Equipment { Weapon } // Optionally add more tables.

union是C中的概念,一个union中可以放置多种类型,共同使用一个内存区域。这里的使用是互斥的,即这块内存区域只能由其中一种类型使用。相对struct来说比较节省内存。
union跟enum比较类似,但是union包含的是table,enum包含的是scalar或者struct。union也只能作为table的一部分,不能作为root type。
在Java中也会生成相应的类,其结构类似enum生成的类。使用方法会在FlatBufferBuilder构造binary buffer的时候说明。

public final class Equipment {
  private Equipment() { }
  public static final byte NONE = 0;
  public static final byte Weapon = 1;

  public static final String[] names = { "NONE", "Weapon", };

  public static String name(int e) { return names[e]; }
}

4. struct关键字

struct Vec3 {
  x:float;
  y:float;
  z:float;
}

struct所有字段都是必填的,因此没有默认值。字段也不能添加或者废弃,且只能包含标量或者其他struct。struct主要用于数据结构不会发生改变的场景,相对table使用更少的内存,lookup的时候速度更快(struct保存在父table中,不需要使用vtable)。

struct在Java中会生成一个继承Struct类的子类,自带create方法用于创建对象:

public final class Vec3 extends Struct {
  public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
  public Vec3 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }

  public float x() { return bb.getFloat(bb_pos + 0); }
  public float y() { return bb.getFloat(bb_pos + 4); }
  public float z() { return bb.getFloat(bb_pos + 8); }

  public static int createVec3(FlatBufferBuilder builder, float x, float y, float z) {
    builder.prep(4, 12);
    builder.putFloat(z);
    builder.putFloat(y);
    builder.putFloat(x);
    return builder.offset();
  }
}

5. table关键字

table Monster {
  pos:Vec3;
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];
  color:Color = Blue;
  weapons:[Weapon];
  equipped:Equipment;
}

table中可以包含上面定义的所有类型。字段(Field)包括名称、类型和默认值三部分;每个字段都有默认值,如果没有明确写出则默认为0或者null。
每个字段都不是必须的,可以为每个对象选择要省略的字段,这是FlatBuffers向前和向后兼容的机制。
需要注意的是:

  • 新的字段只能加在table的后面。旧的代码会忽略这个字段,仍然可以正常执行。如果要灵活使用字段的顺序可以使用id属性。
  • 即使字段不再使用了也不能从schema中删除。可以标记为deprecated,在生成代码的时候该字段不会生成,但是这可能会破坏旧代码。
    table生成的Java类是继承Table类的子类,其中带有start和end方法,用于创建对象:
public final class Monster extends Table {
  public static Monster getRootAsMonster(ByteBuffer _bb) { return getRootAsMonster(_bb, new Monster()); }
  public static Monster getRootAsMonster(ByteBuffer _bb, Monster obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
  public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
  public Monster __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }

  public Vec3 pos() { return pos(new Vec3()); }
  public Vec3 pos(Vec3 obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
  public short mana() { int o = __offset(6); return o != 0 ? bb.getShort(o + bb_pos) : 150; }
  public short hp() { int o = __offset(8); return o != 0 ? bb.getShort(o + bb_pos) : 100; }
  public String name() { int o = __offset(10); return o != 0 ? __string(o + bb_pos) : null; }
  public ByteBuffer nameAsByteBuffer() { return __vector_as_bytebuffer(10, 1); }
  public int inventory(int j) { int o = __offset(14); return o != 0 ? bb.get(__vector(o) + j * 1) & 0xFF : 0; }
  public int inventoryLength() { int o = __offset(14); return o != 0 ? __vector_len(o) : 0; }
  public ByteBuffer inventoryAsByteBuffer() { return __vector_as_bytebuffer(14, 1); }
  public byte color() { int o = __offset(16); return o != 0 ? bb.get(o + bb_pos) : 2; }
  public Weapon weapons(int j) { return weapons(new Weapon(), j); }
  public Weapon weapons(Weapon obj, int j) { int o = __offset(18); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
  public int weaponsLength() { int o = __offset(18); return o != 0 ? __vector_len(o) : 0; }
  public byte equippedType() { int o = __offset(20); return o != 0 ? bb.get(o + bb_pos) : 0; }
  public Table equipped(Table obj) { int o = __offset(22); return o != 0 ? __union(obj, o) : null; }
  public Vec3 path(int j) { return path(new Vec3(), j); }
  public Vec3 path(Vec3 obj, int j) { int o = __offset(24); return o != 0 ? obj.__assign(__vector(o) + j * 12, bb) : null; }
  public int pathLength() { int o = __offset(24); return o != 0 ? __vector_len(o) : 0; }

  public static void startMonster(FlatBufferBuilder builder) { builder.startObject(11); }
  public static void addPos(FlatBufferBuilder builder, int posOffset) { builder.addStruct(0, posOffset, 0); }
  public static void addMana(FlatBufferBuilder builder, short mana) { builder.addShort(1, mana, 150); }
  public static void addHp(FlatBufferBuilder builder, short hp) { builder.addShort(2, hp, 100); }
  public static void addName(FlatBufferBuilder builder, int nameOffset) { builder.addOffset(3, nameOffset, 0); }
  public static void addInventory(FlatBufferBuilder builder, int inventoryOffset) { builder.addOffset(5, inventoryOffset, 0); }
  public static int createInventoryVector(FlatBufferBuilder builder, byte[] data) { builder.startVector(1, data.length, 1); for (int i = data.length - 1; i >= 0; i--) builder.addByte(data[i]); return builder.endVector(); }
  public static void startInventoryVector(FlatBufferBuilder builder, int numElems) { builder.startVector(1, numElems, 1); }
  public static void addColor(FlatBufferBuilder builder, byte color) { builder.addByte(6, color, 2); }
  public static void addWeapons(FlatBufferBuilder builder, int weaponsOffset) { builder.addOffset(7, weaponsOffset, 0); }
  public static int createWeaponsVector(FlatBufferBuilder builder, int[] data) { builder.startVector(4, data.length, 4); for (int i = data.length - 1; i >= 0; i--) builder.addOffset(data[i]); return builder.endVector(); }
  public static void startWeaponsVector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems, 4); }
  public static void addEquippedType(FlatBufferBuilder builder, byte equippedType) { builder.addByte(8, equippedType, 0); }
  public static void addEquipped(FlatBufferBuilder builder, int equippedOffset) { builder.addOffset(9, equippedOffset, 0); }
  public static void addPath(FlatBufferBuilder builder, int pathOffset) { builder.addOffset(10, pathOffset, 0); }
  public static void startPathVector(FlatBufferBuilder builder, int numElems) { builder.startVector(12, numElems, 4); }
  public static int endMonster(FlatBufferBuilder builder) {
    int o = builder.endObject();
    return o;
  }
  public static void finishMonsterBuffer(FlatBufferBuilder builder, int offset) { builder.finish(offset); }
}

6. root_type关键字

root_type Monster;
用于指定序列化后的数据的root table。

7. Types

内置的标量类型:

  • 8 bit: byte, ubyte, bool
  • 16 bit: short, ushort
  • 32 bit: int, uint, float
  • 64 bit: long, ulong, double

内置的非标量类型:

  • 任何类型的Vector([type]表示),不支持嵌套的vector,如果需要可以将vector包装在table中。
  • string,只能使用UTF-8 or 7-bit ASCII,对于其他编码使用vector[byte] 或者 [ubyte]

8. 总结

FlatBuffers的Schema定义与其他框架使用的IDL语言类似,还是比较容易看懂。下一篇将对FlatBufferBuilder进行详细讲解。

你可能感兴趣的:(FlatBuffers Schema解析)