目录
[2.1.vcpkg(Windows 首选,最简单,带 flatc)](#2.1.vcpkg(Windows 首选,最简单,带 flatc))
[2.2.源码编译(跨平台,Windows / Linux / 银河麒麟)](#2.2.源码编译(跨平台,Windows / Linux / 银河麒麟))
[2.3.add_subdirectory 直接嵌入工程(推荐项目集成,不用全局安装)](#2.3.add_subdirectory 直接嵌入工程(推荐项目集成,不用全局安装))
1.简介
FlatBuffers是Google 开源、Apache2.0 协议的零拷贝二进制序列化库 ,最初为游戏开发设计,核心亮点:不需要解析 / 反序列化,直接在原始 buffer 上访问数据。
核心原理:
传统序列化格式(如 JSON, Protocol Buffers)读取数据时,通常需要先将二进制流解析成内存中的对象结构,这个过程会带来额外的内存分配和 CPU 开销。
FlatBuffer 的设计思路则完全不同,它将数据组织成一个扁平的二进制缓冲区(Buffer), 数据在内存 / 文件里的布局就是最终可访问结构,靠偏移量 (offset)+vtable定位字段;支持 mmap,超大文件可以懒加载、随机访问,不用一次性全读入内存FlatBuffer。其工作原理如下:
1.基于偏移量的扁平结构 :FlatBuffer 将所有的对象(如 struct、table、vector)都存放在同一个连续的内存块中,并通过偏移量(Offset) 来建立它们之间的层级关系。这使得数据结构就像指针构成的网络,可以直接"就地"访问。
2."虚拟表"实现兼容性 :为了实现向后/向前兼容(格式进化),FlatBuffer 的核心数据结构 table 使用了一种称为 vtable(虚拟表) 的间接层。每个 table 实例都关联一个 vtable,其中记录了该实例中各个字段的存储位置。如果某个字段在写入时不存在(例如使用了旧版本的 schema),vtable 会指示其"缺席",读取时就会返回该字段的默认值。这种设计使得在不破坏旧数据的情况下增删字段成为可能。
3.严格的规则:为了确保跨平台兼容性,FlatBuffer 对内存对齐和字节序(始终使用小端序)有严格的规定。
2.安装与集成
2.1.vcpkg(Windows 首选,最简单,带 flatc)
cpp
# 安装
vcpkg install flatbuffers
# 指定64位
vcpkg install flatbuffers:x64-windows
CMake直接:
cpp
find_package(flatbuffers CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE flatbuffers::flatbuffers)
装好后
flatc.exe在vcpkg/installed/x64-windows/tools/flatbuffers/
2.2.源码编译(跨平台,Windows / Linux / 银河麒麟)
cpp
git clone https://github.com/google/flatbuffers.git
cd flatbuffers
mkdir build && cd build
Windows(VS2022)
cpp
cmake .. -G "Visual Studio 17 2022" -A x64 -DFLATBUFFERS_BUILD_FLATC=ON -DFLATBUFFERS_BUILD_TESTS=OFF
cmake --build . --config Release
# 安装到系统
cmake --install . --config Release
Linux / 麒麟 /macOS
cpp
cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DFLATBUFFERS_BUILD_FLATC=ON -DFLATBUFFERS_BUILD_TESTS=OFF
make -j$(nproc)
sudo make install
编译产物:
build/flatc:schema 编译器(必须)- 头文件:
include/flatbuffers/ - 静态库:
libflatbuffers.a/flatbuffers.lib
CMake 关键选项:
FLATBUFFERS_BUILD_FLATC=ON:编译 flatc(默认 ON)FLATBUFFERS_BUILD_TESTS=OFF:关掉测试,提速FLATBUFFERS_BUILD_SHAREDLIB=ON:编译动态库(默认 OFF,静态)FLATBUFFERS_INSTALL=ON:make install 生效
2.3.add_subdirectory 直接嵌入工程(推荐项目集成,不用全局安装)
把 flatbuffers 源码放到项目里,CMakeLists.txt:
cpp
set(FLATBUFFERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/flatbuffers")
add_subdirectory(${FLATBUFFERS_SRC} EXCLUDE_FROM_ALL)
target_link_libraries(your_target PRIVATE flatbuffers)
优点:版本锁定、Debug/Release 一致、不会出现库不匹配,仿真项目很适合这个方式
3.使用示例
目录结构:
cpp
flatbuf_demo/
├── monster.fbs
├── main.cpp
└── CMakeLists.txt
monster.fbs:
cpp
namespace Game;
table Monster {
name:string;
hp:int = 100;
mana:int;
}
root_type Monster;
生成代码命令(先装 flatc):
cpp
# 生成C++头文件,输出到当前目录
flatc --cpp monster.fbs
执行后会生成:monster_generated.h
main.cpp
cpp
#include <iostream>
#include "monster_generated.h"
#include "flatbuffers/flatbuffers.h"
using namespace flatbuffers;
using namespace Game;
int main()
{
// ========== 序列化:构建Monster ==========
FlatBufferBuilder builder(1024);
// 先构建字符串(flatbuffer要求子对象先创建)
auto name = builder.CreateString("Orc");
// 构造table
auto monster = CreateMonster(builder, name, 80, 40);
// 设置根对象
builder.Finish(monster);
// 拿到buffer指针和长度
const uint8_t* buf = builder.GetBufferPointer();
size_t buf_size = builder.GetSize();
std::cout << "buffer size: " << buf_size << "\n";
// ========== 反序列化:零拷贝读取,无解析 ==========
auto read_monster = GetMonster(buf);
std::cout << "name: " << read_monster->name()->c_str() << "\n";
std::cout << "hp: " << read_monster->hp() << "\n";
std::cout << "mana: " << read_monster->mana() << "\n";
// 可选:判断字段是否存在(兼容老版本数据)
if (read_monster->has_mana())
{
std::cout << "mana field exists\n";
}
return 0;
}
CMakeLists.txt
cpp
cmake_minimum_required(VERSION 3.14)
project(flatbuf_demo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 方式A:flatbuffers源码放到子目录,add_subdirectory
# add_subdirectory(flatbuffers)
# 方式B:find_package(推荐vcpkg安装flatbuffers)
find_package(FlatBuffers REQUIRED)
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE flatbuffers::flatbuffers)
输出示例:
buffer size: 32
name: Orc
hp: 80
mana: 40
mana field exists
4.优缺点分析
优势
-
极致的性能 :读取数据时无需解析、无需解包、无需额外的内存分配 。访问速度接近直接访问原始结构体,仅多一次通过
vtable的间接寻址。 -
内存高效 :访问数据所需的内存就是缓冲区本身的大小。它还支持
mmap(内存映射),可以只加载缓冲区的一部分到内存中,非常适合处理大文件。 -
出色的兼容性 :通过
table和vtable机制,Schema 可以灵活演进,同时保持对旧数据的向后和向前兼容。 -
代码 footprint 小:生成的代码量小,且核心依赖仅有一个头文件,易于集成。
-
强类型:通过 Schema 定义数据结构,许多错误在编译期就能被发现。
劣势与考量
-
Schema 依赖:读取数据时必须拥有对应的 Schema,无法像 JSON 那样在没有 schema 的情况下"探索"数据。
-
可读性差:序列化后是二进制格式,人类不可直接阅读。不过,FlatBuffers 提供了可选的 JSON 文本格式作为辅助。
-
"可变"的代价 :由于其设计目标是"读取友好",在构建和修改 FlatBuffer 时(通过
FlatBufferBuilder)可能比一些其他格式更复杂。 -
设计取舍:对于数据结构频繁剧烈变化、或对可读性要求极高的场景,JSON 或 Protobuf 可能是更直接的选择。
5.FlatBuffers与Protobuf比较
| 项目 | FlatBuffers | Protobuf |
|---|---|---|
| 读取 | 零拷贝、直接访问 | 需要解析、生成对象,有分配开销 |
| 编码 | builder 逆序构建,写慢 | 简单,写更方便 |
| 体积 | 偏大 | 更小(varint) |
| RPC | 无原生支持 | 原生搭配 gRPC |
| 最佳场景 | 资源文件、mmap、读远大于写 | 网络 RPC、消息通信 |
6.典型应用场景
FlatBuffers 的设计初衷就是为了应对性能极度敏感 和内存受限的环境。
-
游戏开发:这是 FlatBuffers 的"主场"。用于游戏资源(如关卡、模型、配置)的打包和加载,可以极大缩短加载时间并降低运行时内存开销。它由 Google 最初为游戏开发而创建。
-
移动端与嵌入式系统:在内存和带宽有限的设备上,FlatBuffers 的低开销特性优势明显。
-
高性能后端服务:在需要处理高吞吐量、低延迟数据的服务中,例如作为 RPC 框架的序列化层,可以减少 CPU 在序列化/反序列化上的消耗。
-
需要处理大型数据文件的场景 :借助
mmap支持,可以像访问内存一样直接访问磁盘上的大型 FlatBuffer 文件,而无需将其完全加载。
FlatBuffers 是一种以读取性能 和内存效率为最高优先级的序列化方案。如果你的应用场景(如游戏、实时数据处理、移动端)对这两点有极致要求,并且可以接受其基于 Schema 的开发模式,那么它是一个非常值得考虑的选择。