惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
L
LangChain Blog
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
D
DataBreaches.Net
P
Proofpoint News Feed
小众软件
小众软件
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
雷峰网
雷峰网
G
Google Developers Blog

博客园 - PKICA

单片机 MCU,嵌入式,MPU,DSP,FPGA,PLC 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 rust线程-std::thread::park和unpark 配合Builder实现轻量级的线程挂起与唤醒 rust自定义线程属性std::thread::Builder rust线程 rust关联函数 rust高并发设计实践进阶 rust高并发设计实践 rust性能优化与安全边界 联合体union在rust和C语言中有什么区别 rust并发与异步编程 如何预防rust不良的代码设计总结 rust类型系统与零成本抽象 rust内存模型 告别大显存依赖!用 Rust 新一代深度学习框架 Burn 打造纯 CPU 文本分类推理引擎 rust类型系统标记 编译配置解答 git实用命令 rust底层设计理念值得注意的几个地方总结
c++ unordered_map‌底层实现
PKICA · 2026-09-17 · via 博客园 - PKICA

C++ unordered_map 底层实现

unordered_map 底层是哈希表,采用链地址法解决哈希冲突,C++标准只规定接口和复杂度,不强制底层细节,主流实现(GCC libstdc++):桶数组 + 单向链表

头文件:#include <unordered_map> 存储单元:pair<const Key, T>,key 不可修改

1. 整体结构

bucket array(桶数组,vector实现)
├─ bucket0 → 节点1 → 节点2 → nullptr
├─ bucket1 → 节点3 → nullptr
├─ bucket2 → nullptr
└─ bucket3 → 节点4 → 节点5 → nullptr
  • 桶(bucket):桶数组里的一个位置,挂一条单向链表。
  • 节点:存放 pair<const Key, T>,以及链表下一个节点指针。

插入流程

  1. 调用哈希函数 hash<Key>(key),得到哈希值;
  2. 哈希值对桶数量取模,得到桶下标;
  3. 遍历该桶链表,用 == 判断key是否已存在:
    • 存在:直接更新value;
    • 不存在:新建节点,插到链表头部(GCC)。

查找流程

  1. 计算key哈希值,定位桶;
  2. 遍历桶内链表,逐个比较key是否相等;
  3. 找到返回迭代器,找不到返回 end()

重点:哈希只用来定位桶,判定key相等必须用 ==。哈希相同 ≠ key相等(哈希冲突)。

2. 模板参数

template<
    class Key,
    class T,
    class Hash = std::hash<Key>,       // 哈希函数对象
    class KeyEqual = std::equal_to<Key>,// key相等判断
    class Allocator = std::allocator<std::pair<const Key, T>>
> class unordered_map;

规则:如果两个key相等,则它们的哈希值必须相等,否则哈希表逻辑错误。

内置类型如 intstd::string 标准库已经提供 std::hash自定义结构体当key,需要自己提供hash函数和==运算符

3. 负载因子 & rehash(重哈希)

$$load_factor = \frac{元素总数}{桶的数量}$$

  • max_load_factor():最大负载因子,默认 1.0
  • 当 load_factor > max_load_factor,触发 rehash

rehash做的事情

  1. 开辟一块更大的桶数组(GCC一般选质数个数的桶,减少冲突);
  2. 遍历旧哈希表所有节点,重新计算哈希,迁移到新桶;
  3. 释放旧桶数组内存。

⚠️ rehash 会全部迭代器失效;但元素本身的指针/引用依然有效,节点只是换桶。 reserve(n):预分配桶,预留空间,避免中途多次rehash。

4. 重要特性(高频面试考点)

  1. 无序:遍历顺序不是key大小顺序,也不是插入顺序,每次rehash后遍历顺序会变。
  2. 时间复杂度:平均 O(1),最坏(大量冲突,链表很长)O(n)。
  3. GCC libstdc++ 的 unordered_map桶内永远是单向链表,不会转红黑树!

    这点和 Java HashMap 不一样(Java 链表长度超过阈值转为红黑树),C++标准没有这个要求。

5. unordered_map vs map

 unordered_mapmap
底层 哈希表(链地址) 红黑树
有序 无序 key升序有序
复杂度 平均O(1),最坏O(n) O(logn)
迭代器失效 rehash全部失效;删除仅被删迭代器失效 插入几乎不失效;删除仅被删迭代器失效
自定义key 需要hash函数 + == 需要 < 比较运算符

6. 常见坑

  1. key是const,不能修改key,修改会破坏哈希表;
  2. 自定义key忘记提供hash函数,编译报错;
  3. 频繁插入导致多次rehash,性能抖动,可以提前reserve
  4. 不要依赖遍历顺序。

7. 简易模拟实现(极简版)

#include <vector>
#include <list>
#include <functional>
#include <utility>

template<typename K, typename V>
class MyHashmap {
private:
    std::vector<std::list<std::pair<K,V>>> buckets;
    size_t bucket_num;
    size_t elem_cnt;
public:
    MyHashmap(size_t n = 10) : bucket_num(n), elem_cnt(0), buckets(n) {}

    size_t get_bucket(const K& key) {
        return std::hash<K>{}(key) % bucket_num;
    }

    void insert(const K& key, const V& val) {
        size_t idx = get_bucket(key);
        auto& bucket = buckets[idx];
        for(auto& p : bucket) {
            if(p.first == key) {
                p.second = val;
                return;
            }
        }
        bucket.emplace_back(key, val);
        elem_cnt++;
        // 负载因子超过阈值,rehash
        if((double)elem_cnt / bucket_num > 1.0) {
            rehash();
        }
    }

    void rehash() {
        auto old_buckets = std::move(buckets);
        bucket_num *= 2;
        buckets.resize(bucket_num);
        elem_cnt = 0;
        for(auto& list : old_buckets) {
            for(auto& p : list) {
                insert(p.first, p.second);
            }
        }
    }
};

参考资料: