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进行详细讲解。