首页
/ FlatBuffers Kotlin 使用指南:基于 flatc 生成代码与 JVM/Android 序列化实战

FlatBuffers Kotlin 使用指南:基于 flatc 生成代码与 JVM/Android 序列化实战

2026-09-10 10:15:24作者:沈韬淼Beryl

本篇技术指南聚焦 FlatBuffers 在 Kotlin 语言中的完整使用方法:如何通过 flatc --kotlin.fbs schema 生成 Kotlin 代码、如何借助 Java 运行时库读写 FlatBuffer 二进制数据,以及 Kotlin 生成代码相对 Java 的语法差异与命名规则。读完本文,你将能够独立完成 schema 编写、代码生成、数据序列化/反序列化、就地修改(mutation)与字典查找等完整流程,并在自己的 JVM 或 Android 项目中落地。

阅读前提

在深入 Kotlin 特有细节之前,建议先掌握以下 FlatBuffers 通用知识,它们共同构成了使用 Kotlin 绑定的完整闭环:

  • 通用教程:面向全部支持语言的完整入门指南,涵盖从 schema 编写到序列化/反序列化的全流程,是理解 Kotlin 用法的前置基础;
  • 构建文档:说明如何从源码构建 flatc 编译器;
  • schema 编译器文档flatc 的全部命令行参数与生成器选项;
  • schema 编写文档:FlatBuffers IDL 语法、类型系统与文件标识(file_identifier)等细节。

本文只讨论 Kotlin 语言特有的使用细节与差异。

Kotlin 代码的位置与平台支持现状

当前仓库中,Kotlin 生成代码复用 FlatBuffers Java 运行时库,即生成的 .kt 文件直接依赖 com.google.flatbuffers 包下的 Java 类。这意味着:

  • 生成代码只能以 Java 虚拟机(JVM) 为目标架构,天然包含 Android 平台;
  • Kotlin Native 与 Kotlin.js 目前不被支持(这是官方文档的明确结论)。

Java 运行时库源码位于 java/src/main/java/com/google/flatbuffers,核心类包括:

  • FlatBufferBuilder:构建(写入)FlatBuffer 的入口,负责缓冲区的增长与对齐;
  • Table / Struct:生成代码中 table 与 struct 类型的公共基类;
  • ByteBufferUtil:提供 getSizePrefixremoveSizePrefix 等缓冲区工具方法;
  • 一组类型化向量访问类:ByteVectorIntVectorLongVectorFloatVectorDoubleVectorStringVectorBooleanVectorUnionVector 等,用于高效遍历各类型向量;
  • Constants:包含 SIZE_PREFIX_LENGTH(4)等常量定义。

值得留意的是,仓库中已出现一个独立的 Kotlin Multiplatform 实验库目录 kotlin/flatbuffers-kotlin,其源码按 commonMainjvmMainjsMainnativeMain 组织,并附带 commonTest/jvmTest 测试。从源码结构看,这是向 Kotlin 多平台(含 JS/Native)方向演进的新运行时,但目前官方文档声明的 Kotlin 支持仍然基于 Java 运行时,生产使用请以本指南所述方式为准。

使用 flatc --kotlin 生成代码

使用 FlatBuffers 的第一步,是用 schema 编译器生成目标语言的代码。针对 Kotlin,在 flatc 后追加 --kotlin 选项即可(完整生成器选项见 flatc.md):

flatc --kotlin -o <输出目录> <schema.fbs>

常用参数说明:

参数 作用
-o PATH 指定生成文件的输出目录,省略时输出到当前目录
-I PATH 指定 include 语句引用的 schema 查找路径,按给定顺序尝试
--gen-jvmstatic 为 companion object 中的 Kotlin 方法生成 @JvmStatic 注解,便于 Java 侧以静态方式调用(默认不生成)
--gen-onefile 将生成的多个枚举/表代码合并到单一输出文件(适用于 Java、Kotlin、C#、Go、Python)
--filename-suffix SUFFIX 生成文件名的后缀,默认是 _generated
--grpc 同时生成 gRPC 接口桩代码

生成器本身的实现位于 src/idl_gen_kotlin.cpp,其中定义了 Kotlin 代码生成的关键规则,从源码可以确认以下命名约定:

  • 字段与方法采用 lowerCamelCase:例如 schema 中的 test_type 字段生成属性 testTypemutate_test_type 生成 mutateTestType
  • Kotlin 关键字自动转义:当 schema 字段名与 Kotlin 关键字(isinobjectwhenvalvar 等)冲突时,生成器会附加后缀 _ 转义(例如 is_),避免生成非法标识符;
  • 命名空间使用 __ 作为分隔符拼接到包名中;
  • 对象 API 类型后缀为 T(如 MonsterT),与 Java 生成器保持一致;
  • 生成文件扩展名为 .kt

仓库中已生成的 Kotlin 示例代码位于 tests/MyGame/Example(如 Monster.ktAbility.ktColor.ktVec3.kt 等),可作为生成产物的直接参考。

读取 FlatBuffer:从二进制文件到对象访问

生成代码后,将运行时库与生成代码一起加入工程,即可读写 FlatBuffer。官方文档给出了读取 monsterdata_test.mon 二进制文件的完整示例,这里补全 import 并逐行说明:

import MyGame.Example.*
import com.google.flatbuffers.FlatBufferBuilder
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer

// 此代码段忽略异常以保持简洁。
val data = RandomAccessFile(File("monsterdata_test.mon"), "r").use {
    val temp = ByteArray(it.length().toInt())
    it.readFully(temp)
    temp
}

val bb = ByteBuffer.wrap(data)
val monster = Monster.getRootAsMonster(bb)
  • 第一步把二进制文件整体读入 ByteArray
  • 第二步用 ByteBuffer.wrap(data) 包装为 ByteBuffer,注意 FlatBuffers 默认使用小端(LITTLE_ENDIAN)字节序;
  • 第三步调用生成的静态入口 Monster.getRootAsMonster(bb),它内部通过 Monster().__init(bb.position() + bb.getInt(bb.position()), bb) 完成根表定位(对应 tests/MyGame/Example/Monster.kt__init/__assign 的实现)。

拿到 Monster 对象后,即可像访问普通 Kotlin 属性一样读取字段:

val hp = monster.hp          // Short,读取标量字段
val pos = monster.pos!!      // Vec3?,struct 字段是可空引用

上述读取流程与 tests/KotlinTest.ktTestBuffer(bb) 的逻辑完全一致:该测试验证了 hp == 80、默认值 mana == 150name == "MyMonster"、嵌套 struct posx/y/z 坐标、union 字段 testType、向量 inventory 求和以及 testarrayofstring 等读取行为。

数据访问的几种形态

从生成代码 tests/MyGame/Example/Monster.kt 可以看出 Kotlin 生成的访问器形态:

  • 标量字段生成只读 val 属性,未序列化时返回 schema 中声明的默认值(如 mana 默认 150hp 默认 100),例如 val mana: Short get() = ... else 150
  • struct 字段(如 pos)生成可空属性 val pos: Vec3?,同时保留带对象参数的函数重载 pos(obj: Vec3) 以复用对象避免分配;
  • string 字段生成 val name: String(required 字段缺值会抛 AssertionError),并附带 nameAsByteBuffernameInByteBuffer 用于零拷贝访问原始字节;
  • 标量向量(如 inventory)生成 inventory(j: Int): UByte 下标函数与 inventoryLength: Int 长度属性,以及 inventoryAsByteBuffer/inventoryInByteBufferByteBuffer 视图;
  • table/struct 向量(如 test4)生成 test4(j: Int)test4(obj, j) 两种重载;
  • union 字段(如 test)生成 test(obj: Table): Table?,配合类型字段 testType 使用。

构建 FlatBuffer:完整写入流程

Kotlin 同样支持从零构建(写入)FlatBuffer。测试 tests/KotlinTest.ktTestBuilderBasics 给出了一个功能完整的构建示例,其核心模式是"先建子对象,再建表,最后 finish":

val fbb = FlatBufferBuilder(1) // 初始容量 1,刻意触发缓冲区扩容路径

// 1. 创建字符串与向量等子对象,得到 offset
val str = fbb.createString("MyMonster")
val inv = Monster.createInventoryVector(fbb, byteArrayOf(0, 1, 2, 3, 4).asUByteArray())

// 2. start -> add -> end 三段式构建表
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addHp(fbb, 80.toShort())
Monster.addInventory(fbb, inv)
// struct 直接内联构建
Monster.addPos(fbb, Vec3.createVec3(fbb, 1.0f, 2.0f, 3.0f, 3.0, Color.Green, 5.toShort(), 6.toByte()))
val mon = Monster.endMonster(fbb)

// 3. finish 封根,可选 size-prefixed 版本
Monster.finishMonsterBuffer(fbb, mon)
// 或 Monster.finishSizePrefixedMonsterBuffer(fbb, mon)

// 4. 取回数据
val monster = Monster.getRootAsMonster(fbb.dataBuffer())

要点说明:

  • FlatBufferBuilder(initialSize) 的初始容量会显著影响性能:过小会触发频繁扩容与重排,实践中应传一个略大于典型缓冲区大小的值;
  • 向量构建有两种方式:使用生成代码的 createXxxVector(fbb, array) 便捷函数,或手动 startXxxVector(fbb, n) + 逐元素 createXxx + fbb.endVector()
  • finishSizePrefixedMonsterBuffer 会在缓冲区头部写入 4 字节大小前缀,配合 ByteBufferUtil.getSizePrefix / removeSizePrefix 使用(SIZE_PREFIX_LENGTH 常量即 4);
  • 构建完成的缓冲区可通过 fbb.sizedByteArray()fbb.dataBuffer() 输出为字节数组或 ByteBuffer,用于落盘或网络传输;
  • 该测试还演示了字符串池 createSharedString、字节向量 createByteVector、未初始化向量 createUnintializedVector、嵌套 FlatBuffer(createTestnestedflatbufferVector 嵌入子缓冲区并以 testnestedflatbufferAsMonster 解出)、union 向量、createSortedVectorOfTables 排序表向量,以及自定义 FlatBufferBuilder.ByteBufferFactory 实现(如基于 FileChannel.map 的内存映射缓冲区工厂)等进阶能力,均可直接参考。

就地修改(Mutation)与字典查找

FlatBuffers 的另一个特性是无需反序列化即可修改缓冲区中的字段。生成代码为每个标量字段提供 mutateXxx 方法,其语义由 tests/MyGame/Example/Monster.kt 的实现可见:

  • 若字段存在于缓冲区,mutateMana(...) 直接写入对应偏移并返回 true
  • 若字段不存在(未序列化),返回 false,读取时仍返回默认值。

TestBuilderBasics 中的验证逻辑展示了该语义:对未序列化的 mana 调用 mutateMana(10) 返回 falsemana 仍为默认 150;对已存在的 testTypeinventory 向量元素以及 struct 字段(pos.mutateX(55.0f))的修改则成功生效。这对游戏/实时系统中"原地更新单个字段"的场景非常实用。

若 schema 中的表声明了 key 字段,生成代码还会附带字典查找能力:testarrayoftablesByKey("Frodo") 可直接按键值(生成时为 createSortedVectorOfTables 排序)二分查找目标表,测试中验证了 ByKey 查找与排序向量遍历结果的一致性。

可选标量(Optional Scalars)与空值语义

Kotlin 生成代码对可选标量(schema 中声明为 optional 的字段)有着天然自然的映射——可选标量生成可空类型。在 tests/KotlinTest.ktTestScalarOptional 中可以看到:

  • 未设置时,justI8(非可选)返回 0,而 maybeI8(可选)返回 nulldefaultI8 返回默认值 42
  • 覆盖了 i8/u8/i16/u16/i32/u32/i64/u64/f32/f64/bool/enum 全部标量类型;
  • 写入侧同样支持 addMaybeXxx(fbb, value) 显式写入可空字段。

此外 TestNullFields 验证了空缓冲区的读取语义:未序列化的 struct 返回 null、table 返回 null、union 返回 null 且类型为 NONE、各类向量的 xxxLength0xxxAsByteBuffernull。理解这些"缺省即空"的约定,有助于写出健壮的反序列化代码。

Kotlin 与 Java 生成代码的差异

官方文档明确指出:Kotlin 生成代码在设计上尽可能贴近 Java 版本(当前仅支持 JVM 上的 Kotlin),因此实现与使用上的差异基本由 Kotlin 语言自身特性引入,主要体现为两点:

  1. 字段以 Kotlin 属性(properties)形式访问:Java 中的 monster.hp() 方法调用,在 Kotlin 中写作 monster.hp;配合可空类型、默认值 getter 与 mutateXxx 方法,整体访问体验更符合 Kotlin 习惯;
  2. 静态方法集中在 companion object 中getRootAsMonsterstartMonsteraddNameendMonsterfinishMonsterBuffer 等静态入口全部位于类的伴生对象内。若需要从 Java 代码以静态方式调用这些方法,生成时应追加 flatc --gen-jvmstatic 选项,为这些方法添加 @JvmStatic 注解。

其余差异(如字符串处理、异常风格)均为 Kotlin 语言本身与 Java 的常规差异。

运行测试验证环境

仓库为 Kotlin 绑定提供了完整的测试验证手段:

  • 测试源码位于 tests/KotlinTest.kt,覆盖读取 C++ 生成的二进制、Kotlin 侧完整构建、size-prefixed 缓冲区、命名空间嵌套、嵌套 FlatBuffer、union 向量、字符串池、可选标量、字典查找、空字段与就地修改等全部主要特性;
  • 运行脚本为 tests/KotlinTest.sh,其流程为:先用 javac 编译 java/src/main/java/com/google/flatbuffers/*.java 得到 Java 运行时 class 文件,再用 kotlinc 编译全部 *.kt 测试文件并打包为 jar,最后以 kotlin -J"-ea" -cp ... KotlinTest 启用断言运行,成功时输出 FlatBuffers test: completed successfully

前提条件:运行测试脚本要求本机已安装 Kotlin 编译器(kotlinc/kotlin)与 JDK。测试依赖的数据文件 monsterdata_test.mon 位于 tests/monsterdata_test.mon

工程集成小结

将 FlatBuffers 集成到 Kotlin 工程的完整链路为:

  1. 编写 .fbs schema(参考 samples/monster.fbsschema 文档);
  2. 使用 flatc --kotlin 生成 .kt 代码(可叠加 --gen-jvmstatic--gen-onefile 等选项);
  3. java/src/main/java/com/google/flatbuffers 的 Java 运行时(或对应的 jar 依赖)与生成代码一同加入工程,Android 工程可直接引用;
  4. 按本文所述模式读取或构建 FlatBuffer,配合 mutation、字典查找与可选标量等特性满足业务需求;
  5. 借助 tests/KotlinTest.shtests/KotlinTest.kt 验证生成代码与运行时的正确性。

对于更完整的端到端示例,可进一步阅读 通用教程 中 Kotlin 部分的代码块,并结合 tests/MyGame/Example 下的大量生成代码进行对照学习。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23