FlatBuffers 实战教程:从.fbsSchema 编写到多语言序列化与反序列化
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
本篇教程以 FlatBuffers 官方示例中的monster.fbs为贯穿始终的主线,完整讲解如何编写 Schema、使用flatc编译器生成代码、将生成代码集成进应用、利用FlatBufferBuilder完成序列化,以及通过"零反序列化"的方式读取二进制数据。读完本文,你将掌握 FlatBuffers 从 schema 定义到多语言端到端使用的完整流程,并理解 table、struct、vector、union、root_type 等核心类型系统在底层的真实行为。文中所有示例均以当前仓库 samples/monster.fbs 为基准,并辅以 include/flatbuffers/flatbuffer_builder.h、tests/monster_test.cpp 等源码佐证实现细节。
教程概览:FlatBuffers 的五大步骤
本教程覆盖在应用中从头使用 FlatBuffers 的全部环节,整体流程如下:
- 编写 FlatBuffers Schema 文件(
.fbs),定义待序列化数据结构的格式; - 使用
flatc编译器将 Schema 转换为特定语言的代码; - 将生成的代码与运行时库导入应用;
- 序列化数据,将对象写入 FlatBuffer 二进制缓冲;
- 反序列化(访问)FlatBuffer,读取其中的数据。
教程刻意保持语言无关的组织方式,语言差异通过各语言代码块呈现。它覆盖 FlatBuffers 的主要组成部分与类型系统,用于给出整体概览;它并不追求穷举全部特性,也不保证给出"最优"写法。
FlatBuffers Schema(.fbs)
使用 FlatBuffers 的第一步是创建 Schema 文件。Schema 定义了你希望序列化的数据结构格式,由flatc编译器处理以生成可在项目中使用的、特定语言的代码。
本教程使用下面的monster.fbsSchema,它来自 FlatBuffers 的示例代码目录,用于演示完整的示例二进制。
FlatBuffers Schema 是一种接口定义语言(IDL,Interface Definition Language),内含若干数据结构。完整语法细节见 Schema 文档 与 grammar 文档。下面通过行内代码注解快速说明 Schema 每个部分的作用。
// Example IDL file for our monster's schema. namespace MyGame.Sample; //(1)! enum Color:byte { Red = 0, Green, Blue = 2 } //(2)! // Optionally add more tables. union Equipment { Weapon } //(3)! struct Vec3 { //(4)! x:float; //(5)! y:float; z:float; } table Monster { //(6)! pos:Vec3; //(7)! mana:short = 150; //(8)! hp:short = 100; name:string; //(9)! friendly:bool = false (deprecated); //(10)! inventory:[ubyte]; //(11)! color:Color = Blue; weapons:[Weapon]; //(12)! equipped:Equipment; //(13)! path:[Vec3]; } table Weapon { name:string; damage:short; } root_type Monster; //(14)!各注解说明:
- namespace(命名空间):FlatBuffers 支持用命名空间把生成的代码放到指定位置。不同语言对命名空间的支持程度不一(部分语言没有命名空间概念),但对 C 系列语言(C/C++/C# 等)有完整支持。
- enum(枚举):枚举可指定底层数值类型(这里是
byte)。支持隐式编号,因此Green自动取值为 1。 - union(联合体):union 表示从一组可能取值中选出单个值,本质上是"一个枚举(记录实际存储的类型)+ 一个值"的组合。本例中 union 只有一个类型
Weapon,因此实用性有限,仅用于演示。 - struct(结构体):struct 是带名字的标量字段集合,它本身是标量类型,内存占用更小、查找更快。但 struct 一旦定义就不可更改;需要随时间演化的数据结构请使用 table。
- 标量类型:FlatBuffers 提供标准标量数值类型集合:
int8、int16、int32、int64、uint8、uint16、uint32、uint64、float、double,以及bool。注意标量是固定宽度的,不支持 varint(变长整数)。 - table(表):table 是组合数据的主要结构。它可以随时间演化——通过新增字段或弃用(deprecated)字段,同时保持向前与向后兼容。
- struct 字段:
pos:Vec3是 struct 类型字段,意味着Vec3的数据会被内联(inline)序列化在 table 中,无需任何 offset 引用。 - 默认值:字段可以指定默认值。配置了默认值的字段在序列化时可以被完全省略,反序列化时仍能返回该默认值。但默认值一旦设定就不能更改(schema 演进时尤其要注意,详见下文"默认值"小节)。
- string 字段:
name:string指向一个序列化在 table 之外的字符串。 - deprecated 字段:
friendly:bool = false (deprecated)表示该字段已弃用、不再使用,用于替代直接删除字段的做法,从而保持兼容性。 - vector 字段:
inventory:[ubyte]指向一段字节向量。与 string 类似,向量数据序列化在别处,此字段只保存指向向量的 offset。 - table/struct 向量:
weapons:[Weapon]说明 table 与 struct 的向量也是允许的。 - union 字段:
equipped:Equipment是 union 类型字段。 - root_type(根类型):FlatBuffer 的根对象永远是
table。root_type Monster指明反序列化时缓冲区"入口点"所指向的 table 类型。
从仓库源码看,
tests/monster_test.cpp中通过GetMonster(flatbuf)访问根对象并断言monster->hp() == 80、monster->mana() == 150(默认值)等,正是对上述 Schema 语义的落地验证。
三种字段缺失语义:默认值 / Optional / Required
Schema 文档进一步解释了 table 字段在二进制数据中缺失时的三种互斥反应(见 docs/source/schema.md):
- 默认值(Default):按 Schema 中定义的默认值返回;未显式指定默认值的标量类型返回
0,其他类型返回null。只有标量可以显式声明默认值,string/vector/table 等非标量字段缺失时一律为null。注意不要随意修改已经发布的默认值:值为默认值的字段实际上不会写入序列化数据,旧 schema 代码写入的值若恰好等于默认值,会被新 schema 代码读成不同值。 - Optional:字段设为
hp:short = null后,生产者未显式设置时该字段被标记为null,语言侧返回null/std::optional<T>。并非所有语言都支持标量默认值。 - Required:字段标记
(required)后,若未被设置,FlatBuffers verifier 会判定整个缓冲区无效。required不能与显式默认值同时使用,否则编译报错。
编译 Schema 生成代码(flatc)
Schema 写好后,需要把它编译成目标语言的代码。这一步由 FlatBuffers 编译器(flatc)完成,它是仓库中构建出的二进制之一。
构建flatc
FlatBuffers 使用cmake为你的环境生成工程文件。Unix 环境下:
cmake -G "Unix Makefiles" make flatcWindows 环境下:
cmake -G "Visual Studio 17 2022" msbuild.exe FlatBuffers.sln更详细的构建说明(含 MacOS 的 Xcode 工程、FLATBUFFERS_STRICT_MODE严格模式、Bazel 构建等)见 构建文档。部分语言也通过其包管理器提供预编译的flatc。
编译 Schema
调用flatc时传入 Schema 文件与你想生成代码的语言标志即可。编译会生成供应用包含的文件,这些文件提供序列化与反序列化 FlatBuffer 二进制的便捷 API。为monster.fbs生成各语言代码的命令如下:
| 语言 | 命令 |
|---|---|
| C++ | flatc --cpp monster.fbs |
| C | 使用独立项目 FlatCC(见下文) |
| C# | flatc --csharp monster.fbs |
| Dart | flatc --dart monster.fbs |
| Go | flatc --go monster.fbs |
| Java | flatc --java monster.fbs |
| JavaScript | flatc --js monster.fbs |
| Kotlin | flatc --kotlin monster.fbs |
| Lobster | flatc --lobster monster.fbs |
| Lua | flatc --lua monster.fbs |
| PHP | flatc --php monster.fbs |
| Python | flatc --python monster.fbs |
| Rust | flatc --rust monster.fbs |
| Swift | flatc --swift monster.fbs |
| TypeScript | flatc --ts monster.fbs |
仓库 src 目录下的idl_gen_*.cpp(如 idl_gen_cpp.cpp、idl_gen_go.cpp、idl_gen_python.cpp)分别实现了各语言代码生成器,感兴趣可深入阅读。
C 语言的特别说明:如果使用纯 C,需要改用独立项目FlatCC(含 C 语言用的 Schema 编译器与运行时库)。请务必区分
flatc与flatcc两个工具。FlatCC 的典型用法:cd flatcc mkdir -p build/tmp/samples/monster bin/flatcc -a -o build/tmp/samples/monster samples/monster/monster.fbs # 或直接运行 flatcc/samples/monster/build.sh
你可以用一种语言序列化、用另一种语言反序列化 FlatBuffer。为简化教程,这里假设序列化与反序列化使用同一种语言。
flatc的常用生成选项
除语言标志外,flatc还支持丰富的选项(详见 flatc 文档):
-o PATH:将所有生成文件输出到 PATH(绝对或相对路径),默认当前目录;-I PATH:指定include语句查找路径;-b/--binary:根据 Schema 将 JSON 数据文件序列化为二进制,如flatc --binary myschema.fbs mydata.json生成mydata_wire.bin;-j/--json反向将二进制转 JSON(无file_identifier时需加--raw-binary);--grpc:同时生成 gRPC RPC 桩代码(并非所有语言可用);--gen-mutable:生成可原地修改 FlatBuffer 的非 const 访问器;--gen-object-api:生成更便捷的对象式 API(以对象分配为代价);--gen-onefile:为 C#/Go/Java/Kotlin/Python 生成单一输出文件;--cpp-std CPP_STD:选择 C++ 标准(c++0x/c++11/c++17);--scoped-enums使用 C++11 强类型枚举;--strict-json、--defaults-json、--natural-utf8等控制 JSON 输入输出行为。
应用集成
生成的代码随后被包含进你的项目并编译进应用。这高度依赖你的构建系统与语言,但通常包含两件事:
- 导入生成的代码(由
flatc生成); - 导入"运行时"库(各语言的 FlatBuffers runtime)。
以 C++ 为例(其余语言见原文档对应的代码块):
#include "monster_generated.h" // 由 `flatc` 生成 #include "flatbuffers.h" // C++ 运行时库 // 简化下面的命名。 using namespace MyGame::Sample; // 在 Schema 中指定。运行时库的形态因语言而异:有些语言只是需要编译进应用的代码文件(例如 include/flatbuffers 下的 C++ 头文件、go 目录下的 Go 源码、python/flatbuffers 目录下的 Python 源码),另一些语言则通过包管理器提供打包库(如 Dart 的pubspec.yaml、Rust 的Cargo.toml)。
生成的代码同时包含序列化与反序列化 API,因此生产方与消费方的集成步骤完全相同。
序列化
所有文件集成进应用后,就可以开始序列化数据了。
FlatBuffers 的序列化会略显繁琐:每块数据必须单独、按特定顺序(深度优先、先序遍历)序列化。这种繁琐换来的是无需堆分配的、高效的序列化过程,代价是序列化 API 更复杂。例如,任何引用类型(table、vector、string)都必须在其被其他结构引用之前先序列化,因此典型做法是从叶子节点向根节点逐层序列化,如下文所示。
FlatBufferBuilder
大多数语言使用 Builder 对象管理数据序列化进入的二进制数组。它提供序列化数据的 API,并维护一些内部状态;生成的代码把 Builder 上的方法包装成语义贴合 Schema 的 API。
首先实例化一个 Builder(或复用已有实例)并指定内存大小,Builder 会在必要时自动扩容后备缓冲:
// 构造一个 1024 字节后备数组的 Builder。 flatbuffers::FlatBufferBuilder builder(1024);其余语言的构造方式:C#new FlatBufferBuilder(1024)、Javanew FlatBufferBuilder(1024)、Goflatbuffers.NewBuilder(1024)、Pythonflatbuffers.Builder(1024)、RustFlatBufferBuilder::with_capacity(1024)、TypeScriptnew flatbuffers.Builder(1024)等。Builder 就绪后,即可通过 Builder API 与生成的代码向其中写入数据。
序列化数据
本教程要为游戏构建Monster与Weapon。Weapon是 flatbuffertable,含name:string字段与damage:short数值标量字段:
table Weapon { name:string; damage:short; }字符串(Strings)
由于string是引用类型,必须先序列化它,才能赋给Weapon表的name字段,这通过 Builder 的CreateString方法完成。先序列化两把武器的名字:
flatbuffers::Offset<String> weapon_one_name = builder.CreateString("Sword"); flatbuffers::Offset<String> weapon_two_name = builder.CreateString("Axe");flatbuffers::Offset<>只是一个"带类型"的整数,绑定到特定类型上,让数值 offset 具备更强的类型约束。CreateString真正执行了序列化(字符串数据被拷贝进后备数组)并返回一个 offset——可以把它看作该引用的句柄,一个指向数据在缓冲中位置的"类型化数值偏移"。从源码看,flatbuffer_builder.h 中的CreateString实现支持传入 C 字符串或(char*, len)并返回Offset<String>。
表(Tables)
有了名字之后,可以序列化Weapon表。这里使用flatc生成的辅助函数CreateWeapon,它接收 Builder、武器名字的 offset 以及damage数值:
short weapon_one_damage = 3; short weapon_two_damage = 5; // 使用 `CreateWeapon()` 快捷方式,一次性创建字段齐全的 Weapon。 flatbuffers::Offset<Weapon> sword = CreateWeapon(builder, weapon_one_name, weapon_one_damage); flatbuffers::Offset<Weapon> axe = CreateWeapon(builder, weapon_two_name, weapon_two_damage);以 Go 为例,生成代码给出的是Start/Add/End三段式 API(Java、Python、Lua 等语言类似),两种风格完全等价:
sample.WeaponStart(builder) sample.WeaponAddName(builder, weaponOne) sample.WeaponAddDamage(builder, 3) sword := sample.WeaponEnd(builder)flatc生成的函数(如CreateWeapon)本质上由各种 Builder API 方法组合而成,因此并非必须使用生成代码,但生成代码让写法更简洁紧凑。与CreateString一样,表序列化函数也返回指向序列化后Weapon表的 offset。
字段序列化顺序:没有规定哪些表字段必须先序列化,你可以按任意顺序序列化;也可以不序列化某字段(使用 0 值 offset)来表示
null。
有了Weapon,可以继续序列化Monster。对照 Schema 看,这个表字段更多、类型更杂,其中一部分同样需要预先序列化(原因与先序列化名字字符串一致):
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; path:[Vec3]; }向量(Vectors)
weapons字段是Weapon表的vector。两把Weapon已序列化,只需把这些 offset 序列化成一个vector,Builder 提供多种创建vector的方式:
// 创建之前得到的 offset 的 std::vector。 std::vector<flatbuffers::Offset<Weapon>> weapons_vector; weapons_vector.push_back(sword); weapons_vector.push_back(axe); // 把 std::vector 序列化进缓冲,再次得到一个指向该 vector 的 Offset。 // 完整类型很长,这里用 `auto`,它只是一个"带类型"的数值。 auto weapons = builder.CreateVector(weapons_vector);C# / Java / TypeScript 等语言直接用生成辅助函数Monster.CreateWeaponsVector(builder, weaps);而 Go / Lua 由于 Builder 是向前(prepend)构建,需要按逆序逐个PrependUOffsetT(Go:builder.PrependUOffsetT(axe)再builder.PrependUOffsetT(sword)),最后builder.EndVector(2)。
顺带把另外两个向量字段也序列化了:inventory是标量向量,path是 struct(本质也是标量数据)向量,因此可以直接序列化:
// 创建代表兽人背包的 `vector`,每个数字对应击败他后可拾取的物品。 unsigned char treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; flatbuffers::Offset<flatbuffers::Vector<unsigned char>> inventory = builder.CreateVector(treasure, 10); // 构造两个 `Vec3` struct 的数组。 Vec3 points[] = { Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f) }; // 序列化为 struct 向量。 flatbuffers::Offset<flatbuffers::Vector<Vec3>> path = builder.CreateVectorOfStructs(points, 2);Python 侧用生成辅助与对象式构造:Monster.CreateInventoryVector(builder, range(0, 10)),path通过Vec3T列表调用Monster.CreatePathVector(builder, path_points)。
联合体(Unions)
Monster表最后一个非标量字段是equippedunion。本例直接复用已序列化的Weapon(union 中唯一的类型),无需重新序列化。union 字段会隐式添加一个隐藏的_type字段,记录 union 中实际存储值的类型。序列化 union 时,必须显式设置这个类型字段,同时提供 union 值。
同时把其余标量数据一并序列化,因为我们已具备构造Monster所需的全部值与 offset:
// 创建 Monster 所需其余数据。 auto name = builder.CreateString("Orc"); // 创建位置结构体 auto position = Vec3(1.0f, 2.0f, 3.0f); // 生命值 300,法力值 150。 int hp = 300; int mana = 150; // 最后用 `CreateMonster` 辅助函数一次性设置所有字段。 // 这里通过之前序列化的 `Offset<Weapon>` axe 的 `.Union()` 方法设置 union 字段, // 只需用自动生成的 `Equipment_Weapon` 枚举指明放入 union 的对象类型。 flatbuffers::Offset<Monster> orc = CreateMonster(builder, &position, mana, hp, name, inventory, Color_Red, weapons, Equipment_Weapon, axe.Union(), path);Java / C# / Kotlin / Go / Python / TypeScript 等语言则使用StartMonster/addEquippedType(builder, Equipment.Weapon)/addEquipped(builder, axe)/EndMonster的显式风格(Go 为MonsterAddEquippedType/MonsterAddEquipped)。Rust 用Monster::create(&mut builder, &MonsterArgs{ ..., equipped_type: Equipment::Weapon, equipped: Some(axe.as_union_value()), ..Default::default() })。
收尾(Finishing)
至此,名为 "orc" 的Monster已序列化进 flatbuffer,并拿到了它的 offset。Schema 的root_type也是Monster,万事俱备,可以完成序列化步骤。
在 Builder 上调用对应的finish方法,传入 orc 的 offset,指示该table是之后反序列化缓冲时的"入口点":
// 调用 `Finish()` 告知 builder 该 monster 已完整。 // 也可以调用 `FinishMonsterBuffer(builder, orc);` builder.Finish(orc);各语言对应:Gobuilder.Finish(orc)、Javabuilder.finish(orc)、Rustbuilder.finish(orc, None)、Swiftbuilder.finish(offset: orc)等。C 语言由于使用Monster_create_as_root创建,无需额外的finish调用。
Builder 一旦 finish,就不能再向它序列化更多数据。
访问缓冲(Buffer Access)
flatbuffer 现在可以被存储、发送到网络、压缩或做任何你想做的事。访问原始缓冲如下:
// 必须在 `Finish()` 之后调用。 uint8_t *buf = builder.GetBufferPointer(); // 返回 `GetBufferPointer()` 指向的缓冲大小。 int size = builder.GetSize();各语言取缓冲的方式略有差异(详见原文档代码块):Gobuilder.FinishedBytes()返回[]byte;Java/Kotlin 用builder.dataBuffer()(数据不从 0 开始,而是buf.position()起,长度为buf.remaining()),或用sizedByteArray()拷贝一份;Rustbuilder.finished_data()返回&[u8];Pythonbuilder.Output()返回bytearray;Dart 的builder.finish(orc)直接返回Uint8List。
现在可以把字节写入文件或通过网络发送。缓冲在 Builder 被清空或销毁前一直有效。
务必使用二进制模式:文件模式或传输协议必须设为 BINARY 而非 TEXT。若以文本模式传输 flatbuffer,缓冲会被破坏且难以排查。
反序列化
"反序列化"其实有点名不副实:FlatBuffers 访问数据时不会整体反序列化缓冲,它只"解码"被请求的数据,其余数据保持原样。是否拷贝数据、甚至是否读取,都由应用决定。这里继续用"反序列化"指代从二进制 flatbuffer 中访问数据。
成功创建 orc FlatBuffer 后,数据可保存、可传输。最终某个时刻,需要访问缓冲获取底层数据。反序列化所需的应用集成与序列化完全相同(见上文"应用集成")。
根对象访问(Root Access)
所有对 flatbuffer 数据的访问都必须先经过根对象,每个 flatbuffer 只有一个根对象。生成代码提供根据缓冲获取根对象的函数:
uint8_t *buffer_pointer = /* 你刚读取的数据 */; // 获取缓冲内根对象的视图。 Monster monster = GetMonster(buffer_pointer);其他语言示例:C#Monster.GetRootAsMonster(new ByteBuffer(bytes));JavaMonster.getRootAsMonster(ByteBuffer.wrap(bytes));Gosample.GetRootAsMonster(buf, 0);PythonMyGame.Sample.Monster.Monster.GetRootAs(buf, 0);Rustroot_as_monster(buf).unwrap()。
Go/Python 的 offset 参数:根访问函数通常传
0,这对大多数读取的缓冲都适用。若直接从builder.Bytes读取,则需传入builder.Head()的 offset——因为 Builder 是**反向(从尾部向前)**构建缓冲的,数据未必从 offset 0 开始。
再次提醒:务必以二进制模式读取字节,否则缓冲可能损坏。在大多数语言中,返回的对象只是带便捷访问器的数据"视图",数据通常不会从后备缓冲中拷贝出来,这也意味着后备缓冲必须在视图存续期间保持存活。
表字段访问(Table Access)
查看flatc生成的文件会发现:对每个table,它都会为所有非deprecated字段生成访问器。例如Monster根表的部分访问器:
auto hp = monster->hp(); auto mana = monster->mana(); auto name = monster->name()->c_str();这些访问器应分别返回300、150和"Orc"。默认值150并未存储在mana字段中,我们仍能取回它,因为生成的访问器在缓冲中找不到该字段时返回硬编码的默认值。这正是 FlatBuffers 的"零开销 + 默认值省略"设计:值为默认值的字段不占字节。
部分语言风格差异:C#、Dart、Kotlin、Lobster、Swift 等以属性(property)方式暴露字段(如
monster.Hp、monster.hp),其余语言以访问器方法方式暴露(如monster.hp())。
嵌套对象访问(Nested Object Access)
访问嵌套对象的方式类似,嵌套字段指向另一个对象类型。注意字段可能为null(若未写入)。例如访问Vec3类型的posstruct:
auto pos = monster->pos(); auto x = pos->x(); auto y = pos->y(); auto z = pos->z();其中x、y、z将分别包含1.0、2.0、3.0。Go 有一个性能提示:Pos()每次访问新对象都会创建临时访问器对象,性能敏感时可传入已存在的Vec3指针替代nil以复用对象、减少分配与 GC。
向量访问(Vector Access)
同理,可以用索引访问inventoryvector的元素,也可遍历其长度:
flatbuffers::Vector<unsigned char> inv = monster->inventory(); auto inv_len = inv->size(); auto third_item = inv->Get(2);对应 Javamonster.inventoryLength()/monster.inventory(2);Lua 注意1 起始索引(mon:Inventory(3)才是第三个元素)。
对于 table 的向量(如weapons),访问方式与普通向量类似,但要把结果当作 FlatBuffer table 处理:
flatbuffers::Vector<Weapon> weapons = monster->weapons(); auto weapon_len = weapons->size(); auto second_weapon_name = weapons->Get(1)->name()->str(); auto second_weapon_damage = weapons->Get(1)->damage();联合体访问(Union Access)
最后访问equippedunion字段。与创建 union 时一样,需要同时取回 union 的两部分:类型与数据。先取类型,再据此对数据做动态转换(union 只存储 FlatBuffertable):
auto union_type = monster.equipped_type(); if (union_type == Equipment_Weapon) { // 需要 `static_cast` 到类型 `const Weapon*`。 auto weapon = static_cast<const Weapon*>(monster->equipped()); auto weapon_name = weapon->name()->str(); // "Axe" auto weapon_damage = weapon->damage(); // 5 }其他语言的等价做法:Java/Kotlin 显式转型(Weapon)monster.equipped(new Weapon());Python 用flatbuffers.Table初始化Weapon对象:union_weapon.Init(monster.Equipped().Bytes, monster.Equipped().Pos);Rust 用类型安全的方式monster.equipped_as_weapon().unwrap()(若 union 实际不是该类型会返回None);Swift 用monster.equipped(type: Weapon.self)。
完整示例与验证
上述整个"序列化 → 读取"闭环在仓库 samples/sample_binary.cpp 中有完整的可编译演示:构造FlatBufferBuilder,序列化两把武器、构建weapons向量、创建Monster,builder.Finish(orc)后用GetMonster(builder.GetBufferPointer())读回,并以一系列assert校验hp == 80、mana == 150(默认值)、pos->z() == 3.0f、inv->Get(9) == 9、weapons向量元素及equippedunion("Axe"、damage 5)。仓库还提供了 Python(samples/sample_binary.py)、Go(samples/sample_binary.go)、Java(samples/SampleBinary.java)、C#(samples/SampleBinary.cs)、Lua、Swift、Rust 等多个语言的等价示例。
在 tests/monster_test.cpp 中,还能看到flatbuffers::Verifier与VerifyMonsterBuffer(verifier)的配合用法——生产环境在读取不受信任的数据前,建议先用 verifier 校验缓冲完整性。另外,tests/monsterdata_test.mon 是现成的序列化样例数据,配合flatc --json可直观查看二进制内容对应的 JSON 形态。
结语
通过本教程,你已走完 FlatBuffers 的完整主流程:用.fbsIDL 描述数据结构(table/struct/enum/union/vector/root_type),用flatc编译出目标语言代码,集成运行时库后借助FlatBufferBuilder以"叶子到根"的顺序序列化数据并Finish封口,最终通过根对象访问器零拷贝地读取所需字段。这套模式在几乎所有受支持语言中保持一致,差异仅体现在 API 命名风格(方法 vs 属性、CreateXxxvsStart/Add/End)与 Builder 构建方向(如 Go/Lua 的逆序 prepend)上。更深入的内容——如对象式 API、可变访问器、gRPC 支持、64 位 offset 等——可继续阅读 Schema 文档、flatc 文档 与 演进指南。
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考