跳转至

flat_map

std::flat_map 是 C++23 引入的新容器,位于 <flat_map> 头文件中。它与传统的 std::map 类似,都是有序关联容器,但底层实现和性能特性有显著区别。以下是关键差异和适用场景:


1 底层数据结构

容器 底层结构 内存布局
std::map 红黑树(平衡二叉搜索树) 节点分散分配(指针链接)
std::flat_map 两个动态数组std::vector 连续内存存储(键和值分开或交错)
  • std::map:每个键值对独立分配节点,通过指针连接,适合频繁插入/删除。
  • std::flat_map
  • 通常实现为 两个 std::vector(一个存储键,一个存储值,按键排序)。
  • 单个 std::vector<std::pair<Key, Value>>(类似 std::vector 但保持有序)。

2 性能特点

操作 std::map std::flat_map
查找 (find) O (log n) O (log n)(二分查找,更快因缓存局部性)
插入/删除 O (log n) O (n)(需移动后续元素)
遍历 较慢(指针跳转) 极快(连续内存,CPU 缓存命中率高)
内存占用 较高(每个节点含指针开销) 更低(无指针开销,仅存储键值对)

3 使用场景

3.1 适合 std::flat_map 的情况:

  1. 频繁查找/遍历,极少修改
  2. 例如:配置表、只读或初始化后固定的键值数据集。
  3. 需要极致缓存友好性
  4. 对性能敏感的场景(如游戏、高频交易)。
  5. 内存受限环境
  6. 减少内存碎片和开销(嵌入式系统)。

3.2 适合 std::map 的情况:

  1. 频繁插入/删除
  2. 如动态更新的实时键值集合。
  3. 键或值较大或不可移动
  4. std::flat_map 的插入/删除会导致元素移动。

4 代码示例

4.1 基本用法(与 std::map 类似)

#include <flat_map>
#include <iostream>

int main() {
    std::flat_map<int, std::string> fm = {
        {3, "three"}, {1, "one"}, {4, "four"}
    };
    fm.insert({2, "two"}); // 插入(可能触发元素移动)

    for (const auto& [key, value] : fm) { // 遍历速度极快
        std::cout << key << ": " << value << "\n"; // 按键排序输出
    }

    auto it = fm.find(3); // 二分查找
    if (it != fm.end()) {
        std::cout << "Found: " << it->second << "\n"; // 输出: three
    }
}

4.2 与 std::map 的对比

#include <map>
#include <flat_map>

void compare() {
    std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
    std::flat_map<int, std::string> fm = {{1, "one"}, {2, "two"}};

    // 插入性能差异
    m.insert({3, "three"});    // O(log n),无数据移动
    fm.insert({3, "three"});   // O(n),可能需要移动元素

    // 内存占用对比
    std::cout << sizeof(fm) << "\n"; // 类似 vector(通常 24-48 字节)
    std::cout << sizeof(m) << "\n";  // 包含树结构开销(通常 48 字节)
}

5 注意事项

  1. 插入/删除成本
    std::flat_map 的修改操作需要移动元素,可能触发内存重新分配。
  2. 迭代器稳定性
    修改操作会使所有迭代器、指针、引用失效(类似 std::vector)。
  3. 自定义排序
    需在构造函数中指定比较器(与 std::map 相同):
    auto cmp = [](int a, int b) { return a > b; };
    std::flat_map<int, std::string, decltype(cmp)> fm(cmp);
    
  4. 实现差异
    不同编译器可能采用不同实现(如分开存储键/值或交错存储 std::pair)。

6 总结

  • 选择 std::flat_map:优先考虑查找/遍历性能、内存紧凑性,且数据修改较少时。
  • 选择 std::map:需要频繁插入/删除或元素稳定性时。

std::flat_map 是 C++ 对性能优化需求的响应,通过牺牲修改效率换取查找和内存优势。