首页
/ FlatBuffers 在 Dart 中的完整使用指南:从 flatc 代码生成到 Object API 实战

FlatBuffers 在 Dart 中的完整使用指南:从 flatc 代码生成到 Object API 实战

2026-09-10 13:40:50作者:申梦珏Efrain

本篇技术指南以 FlatBuffers 官方文档中 Dart 语言专项章节(docs/source/languages/dart.md)为核心,结合仓库内 Dart 运行时源码与示例代码,系统讲解如何在 Dart/Flutter 项目中使用 FlatBuffers 完成高效的二进制序列化。读完本文,你将掌握通过 flatc--dart 选项生成 Dart 代码、读取与构建 FlatBuffer、理解 Dart 实现与 Dart SDK 内置实现的差异,以及使用 --gen-object-api 生成更易用的 Object API 进行对象级读写。

前置准备:先掌握 FlatBuffers 通用基础

在使用 Dart 语言特性之前,需要先掌握 FlatBuffers 的通用工作流。官方建议按以下顺序阅读文档:

本页文档(dart.md)的定位是:在通用指南之上,专门覆盖 FlatBuffers 在 Dart 中的独有细节与差异

FlatBuffers Dart 库的代码位置

Dart 运行时库代码位于仓库的 dart/ 目录,核心文件包括:

  • dart/lib/flat_buffers.dart:运行时主库,包含 Builder(构建器)、BufferContext(缓冲上下文)、Reader 系列(标量/字符串/列表读取器)等核心类;
  • dart/lib/flex_buffers.dart:FlexBuffers 的 Dart 实现;
  • dart/lib/src/:其他辅助源码;
  • dart/pubspec.yaml:包名 flat_buffers,版本 25.12.19,要求 Dart SDK >=2.17.0 <4.0.0,并依赖 testpathlints 等开发依赖。

pubspec.yaml 的描述中可以看到,该实现"基于 Dart SDK 团队 Konstantin Scheglov 与 Paul Berry 的原始工作",这正是下文"与 Dart SDK 前端 flat_buffers 的差异"一节的由来。

测试 Dart 库:DartTest.sh 一键验证

Dart 库的测试代码位于 tests/ 目录,官方文档提到的测试入口是 dart_test.dart,但在当前仓库中,测试文件已组织在 dart/test/ 下(如 flat_buffers_test.dartflex_builder_test.dartflex_reader_test.dartflex_types_test.dart 等),并配有多份 *_generated.dart 生成代码与 monsterdata_test.mon 二进制测试数据。

运行测试使用 tests/DartTest.sh 脚本(在 Windows 上可参考 DartTest.bat)。从脚本内容可以看到完整测试流程:

# 检查 Dart SDK 是否安装
command -v dart >/dev/null 2>&1 || {
    echo >&2 "Dart tests require dart to be in path but it's not installed.  Aborting."
    exit 1
}

# 用 flatc 生成测试所需的 Dart 代码(--dart 与 --gen-object-api 同时启用)
../flatc --dart --gen-object-api -I include_test -o ../dart/test monster_test.fbs
../flatc --dart --gen-object-api -I include_test/sub -o ../dart/test include_test/include_test1.fbs
../flatc --dart --gen-object-api -I include_test -o ../dart/test include_test/sub/include_test2.fbs

# 复制测试二进制数据与 schema
cp monsterdata_test.mon ../dart/test
cp monster_test.fbs ../dart/test

cd ../dart
../flatc --dart --gen-object-api -o ./test ./test/enums.fbs
../flatc --dart --gen-object-api -o ./test ./test/bool_structs.fbs

# 更新依赖并执行测试
dart pub get
dart test

注意:脚本要求系统已安装 Dart SDK 并将 dart 命令加入 PATH,同时仓库根目录需存在可执行的 flatc 测试脚本中使用的 --dart --gen-object-api 组合,正是本文后面要重点讲解的两个关键选项。

在 Dart 中使用 FlatBuffers 库

基本流程:flatc 生成 + 运行时读取

FlatBuffers 在 Dart 中同时支持**读取(reading)写入(writing)**二进制 FlatBuffer。使用步骤分为两步:

  1. flatc--dart 选项从 schema 生成 Dart 类,例如:
    flatc --dart monster.fbs
    
  2. 在代码中同时引入运行时库与生成代码,即可读写 FlatBuffer。

读取 FlatBuffer 二进制文件

文档给出了读取 FlatBuffer 二进制文件的完整示例:先将二进制文件读入 List<int>,再传给生成类(如 Monster)的工厂构造函数:

import 'dart:io' as io;

import 'package:flat_buffers/flat_buffers.dart' as fb;
import './monster_my_game.sample_generated.dart' as myGame;

List<int> data = await new io.File('monster.dat').readAsBytes();
var monster = new myGame.Monster(data);

随后即可像访问普通 Dart 对象一样读取字段值:

var hp = monster.hp;
var pos = monster.pos;

对照仓库中的生成代码 dart/example/monster_my_game.sample_generated.dart,可以看到 Monster 的工厂构造函数内部通过 fb.BufferContext.fromBytes(bytes) 创建只读的缓冲上下文,再由 reader.read(rootRef, 0) 定位根对象:

factory Monster(List<int> bytes) {
  final rootRef = fb.BufferContext.fromBytes(bytes);
  return reader.read(rootRef, 0);
}

在运行时库 dart/lib/flat_buffers.dart 中,BufferContext.fromBytes 会把 List<int> 包装成 ByteData 视图(若传入的是 Uint8List 则直接复用其底层 buffer,零拷贝)。字段的读取依赖 Reader.vTableGet 通过 VTable 查找字段偏移:若字段不存在则返回默认值,这正是 FlatBuffers"字段可缺省、向后兼容"的核心机制。

写入 FlatBuffer:Builder 的两种风格

文档提到本实现的代码生成提供两类构建类:ObjectBuilderBuilder 类。仓库示例 dart/example/example.dart 中两种方式均有完整演示:

方式一:底层 Builder(贴近其他语言的 builders,内存更省)

final builder = fb.Builder(initialSize: 1024);
final int? weaponOneName = builder.writeString("Sword");
// ... 依次写入字符串、列表、struct、table
final int monsteroff = monster.finish();
builder.finish(monsteroff);
if (verify(builder.buffer)) {
  print("The FlatBuffer was successfully created with a builder and verified!");
}

这种方式要求按预先顺序(pre-order)构造所有数据,即先写入嵌套的子对象(字符串、vector、struct),最后再构建引用它们的 table。从 MonsterBuilder 的生成代码可以看出,begin() 调用 fbBuilder.startTable(10),各 add* 方法调用 addInt16addOffsetaddStruct 等底层方法,finish() 调用 fbBuilder.endTable() 结束 table 并触发 VTable 去重。Builder 构造参数支持 initialSize(初始缓冲字节数,默认 1024)、internStrings(字符串驻留池)、deduplicateTables(VTable 去重,默认开启)与自定义 Allocator

方式二:ObjectBuilder(更易用,代价是分配更多引用)

var monsterBuilder = my_game.MonsterObjectBuilder(
  pos: my_game.Vec3ObjectBuilder(x: 1.0, y: 2.0, z: 3.0),
  mana: 150,
  hp: 300,
  name: 'Orc',
  inventory: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  color: my_game.Color.Red,
  weapons: [
    my_game.WeaponObjectBuilder(name: 'Sword', damage: 3),
    axe,
  ],
  equippedType: my_game.EquipmentTypeId.Weapon,
  equipped: axe,
);

var buffer = monsterBuilder.toBytes();  // 直接得到序列化后的 Uint8List

ObjectBuildertoBytes() 内部会新建 Builder 并完成 finish,一次性返回可直接落盘或网络传输的字节流。运行时库中 ObjectBuilder 抽象类还提供了 getOrCreateOffset,允许复用同一 Builder 实例中已写入的偏移量。

两种风格对应运行时的两套核心类:

  • 写入侧Builder 负责从缓冲区尾部向头部反向写入,addField/_prepare 处理对齐,endTable 计算并去重 VTable(见 dart/lib/flat_buffers.dart_VTable 类的 _offsetsMatch 逻辑,结构相同的表会共享同一 VTable);
  • 读取侧BufferContext 提供各标量类型的 _get* 系列方法(全部按小端序 Endian.little 读取),Reader 及其子类(Int32ReaderStringReaderListReader 等)负责类型化取值,列表读取器默认惰性读取(lazy),仅在访问元素时才解析,进一步降低反序列化开销。

与 Dart SDK 前端 flat_buffers 的关键差异

本仓库的实现大量借鉴了 Dart SDK front end/analyzer 包内部使用的实现,但做了若干显著改动,官方文档列出了五点,理解这些差异对于从 Dart SDK 迁移到本库尤为重要:

  1. 移除了布尔列表的打包(packed)支持。该特性在其他语言实现中并不标准、互不兼容。与 JavaScript 实现类似,布尔列表中的 null 值会被当作 false 处理。当然,仍然可以在单个标量字段内自行打包位数据,但这需要在应用侧手工完成。
  2. 枚举改用专门的枚举类。Dart SDK 实现使用普通 Dart 枚举,这仅在枚举总是从 1 开始索引时才正确;而 FlatBuffers 并不要求这一点。本实现采用类似枚举的专用类(见 EquipmentTypeIdColor 的生成代码,每个常量包装一个 value 并附带 fromValue 工厂与 values 映射表),确保 FlatBuffers 与 Dart 及其他平台之间的映射正确。
  3. 完整支持 struct 与 struct 向量。SDK 实现似乎不支持 FlatBuffer struct 或 struct 向量,把所有东西都当作内建标量或 table;本实现以与其他非 Dart 实现兼容的方式处理 struct,并正确处理 struct 向量,为此改造了许多以 low 前缀命名的方法。
  4. int64/uint64 不做浮点降级,并新增 16 位整数支持。SDK 实现把 int64/uint64 当作 float64 处理,本实现不会如此,这可能在 JavaScript 兼容性上带来问题——但可以通过直接使用 JavaScript 实现、或定制一个把所有 64 位数字当作浮点数的实现来规避。支持 Dart VM 与 Flutter 是本实现更重要的目标。这也解释了为何运行时库中 Uint64Reader 带有"WARNING: May have compatibility issues with JavaScript"的注释(见 dart/lib/flat_buffers.dart)。
  5. 代码生成同时提供 ObjectBuilder 与 Builder 两类 API。ObjectBuilder 生成的代码与 SDK 中消费 FlatBuffers 的类非常相似,更易用,代价是额外分配更多对象引用;Builder 类则产出更接近其他语言 builder 风格的代码,内存效率更高。

文本解析(JSON/Schema)的限制

当前 Dart 实现尚不支持直接从 Dart 解析文本格式(包括 Schema 与 JSON)。如果需要文本解析能力,可以通过 Dart Native Extensions 调用 C++ 解析器实现——可参考仓库中 src/idl_parser.cppflatc 的 C++ 解析核心)与 src/idl_gen_text.cpp(文本输出)等 C++ 侧实现。需要说明的是:该方案目前不适用于 Flutter(受相关 Flutter 平台问题限制,详情可关注官方 issue 跟踪进展)。对于纯 Dart/Flutter 场景,建议直接消费二进制格式,或在服务端/构建期完成 JSON 到二进制的转换。

基于对象的 API(Object based API,--gen-object-api

FlatBuffers 的立身之本就是内存效率,因此其基础 API 围绕"尽量少分配"设计——这导致 API 使用上较笨拙:要求预顺序构造所有数据,且变更(mutation)困难

当效率不是首要考量时,可以通过 --gen-object-api 选项生成更便捷的基于对象的 API:它能把 FlatBuffer 解包(unpack)成普通对象与列表,从而支持便捷的构造、访问与变更;变更后再**打包(pack)**回新的 FlatBuffer。这一点也在 tests/DartTest.sh 中得到印证——测试生成代码时总是同时传入 --dart --gen-object-api

典型使用方式(unpack → 修改 → pack)

文档给出了完整的三步用法:

// Deserialize from buffer into object.
MonsterT monster = Monster(flatbuffer).unpack();

// Update object directly like a Dart class instance.
print(monster.Name);
monster.Name = "Bob";  // Change the name.

// Serialize into new flatbuffer.
final fbb = Builder();
fbb.Finish(monster.pack(fbb));

MonsterT("T" 后缀即 Object API 生成的普通 Dart 类)为例,unpack() 将 FlatBuffer 展开为可直接读写的 Dart 对象;对属性直接赋值即完成修改;pack(fbb) 则把对象重新序列化进 Builder,最后由 fbb.Finish(...) 产出新的二进制缓冲。整个流程让"读-改-写"变成普通 Dart 对象操作,同时生成的二进制仍与其他语言实现完全互通。

底层机制对应

  • 解包方向:生成类内部基于运行时库的 Reader/BufferContext(见 dart/lib/flat_buffers.dart)逐个字段读取,把 table/struct/vector 全部物化为 Dart 对象与 List
  • 打包方向:pack 利用 BuilderwriteStringwriteList*addOffset 等 API 把对象图写回缓冲区,其中 writeString 支持 asciiOptimization(纯 ASCII 字符串直接拷贝,避免 utf8.encode 的转换开销,见源码注释),并支持通过 internStrings 做字符串驻留去重。

与官方教程的衔接

本文聚焦 Dart 特有的细节;如需更深入、完整的端到端示例(schema 编写 → flatc 生成 → 各语言读写),请参阅 docs/source/tutorial.md。仓库中 dart/example/example.dart 提供了可直接运行的完整读写示例,dart/README.md 则说明了包的发布信息与 flatc 版本对应关系(建议下载与你所用 Dart 包版本匹配的 flatc)。Dart 运行时生成的代码与 C++/Java/Go 等其他语言实现完全二进制互通,这也是 FlatBuffers 跨平台序列化的核心价值所在。

总结

在 Dart 中使用 FlatBuffers 的要点可归纳为:

  1. flatc --dart(可选叠加 --gen-object-api)从 schema 生成代码;
  2. 引入 package:flat_buffers/flat_buffers.dart 运行时库;
  3. 读取侧使用生成类的工厂构造函数 + VTable 驱动的惰性 Reader,高效且零分配访问字段;
  4. 写入侧按"先子后父"的顺序用 Builder 构建,或使用更便捷的 ObjectBuilder/Object API;
  5. 牢记与 Dart SDK 内置实现的五点差异(布尔列表打包、枚举类、struct 支持、64 位整数、双 API 风格),避免迁移踩坑;
  6. 文本解析在纯 Dart 中暂不支持,需借助 C++ 解析器或提前转换。

这些能力使 Dart 与 Flutter 应用能够在移动端、桌面端与 Web 场景下,与其他语言无缝交换高效紧凑的二进制数据。

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527