FlatBuffers: 一个高效的零拷贝二进制序列化库

目录

1.简介

2.安装与集成

[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 直接嵌入工程(推荐项目集成,不用全局安装))

3.使用示例

4.优缺点分析

5.FlatBuffers与Protobuf比较

6.典型应用场景


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 的开发模式,那么它是一个非常值得考虑的选择。

相关推荐
xys_6781 小时前
深挖C++:C++入门基础
开发语言·c++
高冷小鱼殿下2 小时前
C 语言指针与输入输出
c++
路溪非溪2 小时前
C++标准库之智能指针
linux·开发语言·c++
hetao17338373 小时前
2026-11-02 hetao1733837 的刷题记录
c++·算法
Tairitsu_H3 小时前
[C++高阶数据结构·图论下] 最短路径:为什么“挑最小的”一定正确?
数据结构·c++·图论
weixin199701080164 小时前
官方授权通道和灰色爬虫,做数据服务该选哪条路(附 python 源码)
c++·爬虫·python
wuminyu4 小时前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++
Tairitsu_H4 小时前
[C++高阶数据结构·图论上] 最小生成树:为什么“选边”和“选点”是两条路?
数据结构·c++·图论·最小生成树
Bczheng15 小时前
VS2022 安装MFC和QT
c++·qt·mfc