1. 简介
本文将探讨Java中的FlatBuffers库,并演示如何使用它进行序列化和反序列化操作。
2. Java中的序列化
序列化是将Java对象转换为字节流的过程,便于网络传输或文件持久化。Java通过java.io.Serializable
接口和java.io.ObjectOutputStream
/java.io.ObjectInputStream
类提供了内置的序列化机制。
然而,由于其存在处理复杂对象图和依赖类时的繁琐性等缺点,Java社区涌现出多种序列化库。常用库包括Jackson和Gson。 较新的序列化标准是Protocol Buffers,这是Google开发的跨语言二进制序列化格式,广泛应用于高性能环境和分布式系统。
3. FlatBuffers
FlatBuffers是Google开发的高效跨平台序列化库,支持C、C++、Java、Kotlin和Go等多种语言。最初为游戏开发设计,因此性能和低内存开销是其核心设计原则。
FlatBuffers与Protocol Buffers同为Google出品,都是基于二进制的数据格式。两者都支持高效的高速序列化/反序列化,主要区别在于:FlatBuffers无需解包到中间数据结构即可直接访问数据。
3.1. FlatBuffers库简介
完整的FlatBuffers实现包含以下组件:
- FlatBuffer模式文件
-
flatc
编译器 - 序列化/反序列化代码
模式文件作为数据模型结构的模板,语法类似C语言或IDL格式。需先定义模式文件,再用flatc
编译器生成代码。
3.2. 表与模式
FlatBuffer是包含嵌套对象(如结构体、表和向量)的二进制缓冲区,通过偏移量组织数据。
这种设计允许像传统指针结构一样原地遍历数据,但严格遵循对齐和字节序规则(始终为小端序),确保跨平台兼容性。表对象还支持向前/向后兼容。
表是FlatBuffers中最基础的数据结构,用于表示带命名字段的复杂对象,类似其他语言中的类或结构体,支持int、short、string、struct、vector甚至其他表等多种字段类型。
3.3. Flatc编译器
flatc
编译器是FlatBuffers的核心工具,可根据模式文件生成C++、Java等语言的序列化/反序列化代码。编译器接收模式定义,输出目标语言的代码。
后续章节将使用该编译器生成代码,首先需要构建编译器:
$ git clone https://github.com/google/flatbuffers.git
克隆后使用cmake构建:
$ cd flatbuffers
$ mkdir build
$ cd build
$ cmake ..
验证安装成功:
$ ./flatc --version
flatc version 23.5.26
编译文件位于/flatbuffers/build
目录,flatc
可执行文件也在其中。建议创建快捷方式方便使用。
4. 使用FlatBuffers
本节通过游戏地形案例演示FlatBuffers的使用。假设我们开发包含海洋、山地、平原等不同地形的游戏,每种地形有独特属性。地形数据需高效序列化后传输给玩家。
4.1. 模式定义
首先定义地形模式:地形在FlatBuffer中表现为表,包含名称(海洋/陆地/山地等)、颜色和3D坐标位置。地形可附加效果(如沙漠的沙暴、陆地的洪水),效果可定义为独立表。
模式文件terrain.fbs
如下:
namespace MyGame.baeldung;
enum Color:byte { Brown = 0, Red = 1, Green = 2, Blue = 3, White = 4 }
struct Vec3 {
x:float;
y:float;
z:float;
}
table Terrain {
pos:Vec3; // Struct.
name:string;
color:Color = Blue;
navigation: string;
effects: [Effect]
}
table Effect {
name:string;
damage:short;
}
root_type Terrain;
包含颜色枚举、坐标结构体和两个表(Terrain为根类型)。
4.2. 模式编译
使用flatc
编译模式文件:
$ cd <path to schema>
$ flatc --java terrain.fbs
⚠️ 注意:flatc
路径因安装位置而异。
4.3. 创建对象与序列化
模式编译完成后,创建沙漠地形及其效果。添加Maven依赖:
<dependency>
<groupId>com.google.flatbuffers</groupId>
<artifactId>flatbuffers-java</artifactId>
<version>23.5.26</version>
</dependency>
导入生成代码:
import MyGame.terrains.*;
import com.google.flatbuffers.FlatBufferBuilder;
编译生成的Effect
类提供createEffect()
方法。初始化1024字节的构建器:
FlatBufferBuilder builder = new FlatBufferBuilder(INITIAL_BUFFER);
int sandstormOffset = builder.createString("sandstorm");
short damage = 3;
int sandStorm = MyGame.terrains.Effect.createEffect(builder, sandstormOffset, damage);
创建沙漠地形:
byte color = MyGame.terrains.Color.YELLOW;
int terrainName = builder.createString("Desert");
int navigationName = builder.createString("south");
使用Terrain
类的静态方法添加元数据和效果:
int effectOffset = MyGame.terrains.Terrain.createEffectsVector(builder, effects);
startTerrain(builder);
addName(builder, terrainName);
addColor(builder, color);
addNavigation(builder, navigationName);
addEffects(builder, effectOffset);
int desert = endTerrain(builder);
builder.finish(desert);
序列化到缓冲区:
ByteBuffer buf = builder.dataBuffer();
4.4. 反序列化操作
将字节数组转为ByteBuffer
:
ByteBuffer buf = java.nio.ByteBuffer.wrap(buffer);
获取根对象并访问属性:
Terrain myTerrain = Terrain.getRootAsTerrain(buf)
Assert.assertEquals(terrain.name(), "Desert");
Assert.assertEquals(terrain.navigation(), "south");
每个属性都有对应的访问器,访问效果列表:
Effect effect1 = terrain.effectsVector().get(0);
Effect effect2 = terrain.effectsVector().get(2);
Assert.assertEquals(effect1.name(), "Sandstorm");
Assert.assertEquals(effect2.name(), "Drought");
4.5. 修改FlatBuffers
FlatBuffers本质是只读的,但有时需在传输前修改数据(如将沙暴伤害值从3改为10)。此时可使用原地修改功能。
需用–gen-mutable
参数重新编译模式:
$ ./../flatc --gen-mutable --java terrain.fbs
生成代码提供mutate()
方法:
Assert.assertEquals(effect1.damage(), 3);
effect1.mutateDamage((short) 10);
Assert.assertEquals(effect1.damage(), 10);
5. JSON转换
flatc
支持二进制与JSON互转。将JSON转为二进制:
flatc --binary <template file> <json file>
$ flatc --binary terrain.fbs sample_terrain.json
二进制转JSON:
flatc --json --raw-binary <template file> -- <binary file>
$ flatc --json --raw-binary terrain.fbs -- sample_terrain.bin
6. FlatBuffers的优势
使用该库的主要优势包括:
- ✅ 零拷贝访问:数据以扁平二进制存储,无需解析/解包即可直接访问
- ✅ 自动兼容性:数据结构变更时自动保持向前/向后兼容
- ✅ 内存高效:仅需缓冲区大小的内存即可操作数据
- ✅ 代码轻量:生成代码极简,仅需单个头文件依赖
- ✅ 强类型安全:编译时即可捕获类型错误
7. 总结
本文深入探讨了FlatBuffers库的序列化/反序列化能力,通过实战代码演示了其核心功能和使用场景。对于需要高性能数据交换的系统(如游戏、分布式服务),FlatBuffers是值得考虑的方案。
完整代码示例可在GitHub获取。