当前位置: 首页 > news >正文

std::unordered_map(C++)

std::unordered_map

  • 1. 概述
  • 2. 内部实现
  • 3. 性能特征
  • 4. 常用 API
  • 5. 使用示例
  • 6. 自定义哈希与相等比较
  • 7. 注意事项与优化
  • 8. 使用建议
  • 9. emplace和insert异同
    • 相同点
    • 不同点
    • 例子对比
    • 何时优先使用哪种?

1. 概述

  • 定义:std::unordered_map<Key, T, Hash, KeyEqual, Allocator> 是一个基于哈希表(hash table)实现的键值对容器,提供平均 O(1) 的查找、插入和删除性能。

  • 特点:

    • 无序:元素按哈希值分布在若干“桶”(bucket)中,不保证遍历顺序。

    • 唯一键:同一个键最多只能出现一次;如果插入已存在的键,默认不覆盖(可用 operator[] 或 at 修改)。

    • 可自定义:支持自定义哈希函数和键相等比较器,也可指定内存分配器。

2. 内部实现

  1. 哈希桶结构

    • 容器内部维护一组“桶”,每个桶是一个链表(或其他冲突解决结构)。

    • 元素根据 Hash(key) % bucket_count 落入对应桶中。

  2. 装载因子(load factor)

    • 定义为 size() / bucket_count,表示平均每个桶的元素数。

    • 当装载因子超过 max_load_factor() 时,容器会自动进行 rehash(扩容并重新分配桶)。

  3. 再哈希(rehash)

    • 调用 rehash(n) 会将桶数调整为 ≥ n,并将所有元素重新分布。

    • reserve(n) 则是以元素数 n 为基准,确保 bucket_count ≥ n / max_load_factor()。

3. 性能特征

操作平均复杂度最坏情况复杂度备注
find / operator[]O(1)O(n)取决于哈希冲突;若所有元素落在同一桶,退化为链表查找
insert / emplaceO(1)O(n)同上,且可能触发一次 rehash(O(n))
erase(key)O(1)O(n)同上
clearO(n)O(n)
遍历(iteration)O(n + bucket_count)O(n + bucket_count)需跳过空桶
  • 空间开销:

    • 每个桶需存储一个指针或链表头;元素节点通常包含键、值、下一个节点指针,以及可能的桶索引/哈希缓存。
  • 哈希函数好坏:

    • 高质量的哈希函数能显著降低冲突,提高稳定性;如对自定义类型可使用 std::hash 或第三方的高性能哈希。

4. 常用 API

// 构造与析构
std::unordered_map<Key, T> m1;                              // 默认构造
std::unordered_map<Key, T> m2(100);                         // 指定初始桶数
std::unordered_map<Key, T> m3(100, MyHash{}, MyEqual{});    // 指定哈希与比较
// 大小与容量
bool empty() const;
size_t size() const;
size_t bucket_count() const;
float load_factor() const;
float max_load_factor() const;
void rehash(size_t newBucketCount);
void reserve(size_t count);  // 保证能插入 count 个元素而不触发 rehash
// 元素访问
T& operator[](const Key& key);       // 若不存在则插入默认构造的 T
T& at(const Key& key);               // 若不存在抛出 std::out_of_range
// 插入
std::pair<iterator, bool> insert(const value_type& kv);
template< class... Args >
std::pair<iterator, bool> emplace(Args&&... args);
// 查找与删除
iterator find(const Key& key);
size_t erase(const Key& key);
iterator erase(iterator pos);
void clear();
// 遍历
iterator begin() noexcept;
iterator end() noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
// 哈希与比较器查询
size_t bucket(const Key& key) const;
size_t bucket_size(size_t n) const;
Hash hash_function() const;
KeyEqual key_eq() const;

5. 使用示例

#include <unordered_map>
#include <string>
#include <iostream>int main() {// 1. 创建并插入std::unordered_map<std::string, int> wordCount;wordCount["hello"] = 1;                // operator[] 插入或修改wordCount.insert({"world", 2});         // insert 不会覆盖已存在键wordCount.emplace("foo", 3);            // 完美转发构造// 2. 查找auto it = wordCount.find("world");if (it != wordCount.end()) {std::cout << it->first << ": " << it->second << "\n";}// 3. 遍历for (auto& kv : wordCount) {std::cout << kv.first << " => " << kv.second << "\n";}// 4. 保证容量wordCount.reserve(1000); // 预计要插入 1000 条记录,减少 rehash 次数// 5. 统计装载因子std::cout << "load factor: " << wordCount.load_factor() << "\n";return 0;
}

6. 自定义哈希与相等比较

当键类型为自定义结构体时,需要提供 Hash 和 KeyEqual:

struct Point { int x, y; };// 自定义哈希:结合 x,y 的值
struct PointHash {size_t operator()(Point const& p) const noexcept {// 经典做法:位移与异或return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);}
};// 自定义相等比较
struct PointEqual {bool operator()(Point const& a, Point const& b) const noexcept {return a.x == b.x && a.y == b.y;}
};std::unordered_map<Point, std::string, PointHash, PointEqual> mp;

7. 注意事项与优化

  • 避免频繁 rehash

    • 使用 reserve() 预先分配足够桶数,比频繁自动扩容更高效。
  • 迭代顺序不稳定

    • 容器内部槽位和元素分布会随 rehash 而变化,不要依赖遍历顺序。
  • 哈希攻击

    • 在对抗恶意输入的场景中,默认 std::hash 可能遭受冲突攻击,可考虑使用带随机种子的哈希或第三方库(如 CityHash、MurmurHash)。
  • 线程安全

    • 读写同一容器需加锁;C++20 起可在不同线程同时读不同桶,但标准未强制保证,生产环境仍建议加互斥。
  • 内存占用

    • 哈希表相较于红黑树(std::map)通常占用更多内存;应在性能 vs. 空间之间权衡。

8. 使用建议

  • 需要

    • 快速查找/插入/删除 且无需有序遍历时,首选 std::unordered_map。

    • 容器大小可预估,且希望通过 reserve() 控制 rehash 时机。

  • 不需要

    • 保持元素有序或按键排序输出时,应选用 std::map。

    • 需要对容器做范围算法(如二分查找)或有序区间操作时。

9. emplace和insert异同

相同点

  • 功能

    • 都是在 std::unordered_map 中插入元素(键值对)。

    • 若指定键已存在,插入操作不生效,都会返回相同的迭代器和 false。

  • 返回值

    • std::pair<iterator,bool>——iterator 指向新插入或已有元素的位置,bool 表示此次调用是否实际插入了新元素。

不同点

特性insertemplace
接口签名多重重载,典型如 insert(const value_type&)、insert(value_type&&)、insert(initializer_list<value_type>)template<class… Args> emplace(Args&&… args)
参数需要先构造好 value_type(即 pair<const Key, T>)再传入,可能多一次拷贝/移动直接将参数完美转发(perfect‑forward)给底层元素构造函数,就地构造,避免不必要的临时对象
构造方式拷贝 或 移动 已有的 pair原地(in‑place)调用 pair 的构造函数
效率如果要构造 pair,就至少一次临时对象(拷贝/移动)零或更少的拷贝/移动,适合复杂类型或昂贵拷贝的场景
复杂构造支持只能插入已经准备好的 pair支持 piecewise_construct,可以分别传递给 key 和 value 的构造参数

例子对比

std::unordered_map<std::string, std::vector<int>> m;// 1. insert:需要先构造一个 pair,再插入
std::pair<const std::string, std::vector<int>> p("key", {1,2,3});
auto res1 = m.insert(p);                     // 拷贝 p
auto res2 = m.insert({ "key2", {4,5,6} });    // 构造临时 pair,再移动或拷贝// 2. emplace:直接传递构造参数,就地构造
auto res3 = m.emplace("key3", std::vector<int>{7,8,9});
// 等价于:在内部执行 pair("key3", vector{7,8,9}) 的就地构造,无额外拷贝// 3. emplace + piecewise_construct(针对 key 和 value 各自参数包更复杂的场景)
struct Key { Key(int,a){} };
struct Val { Val(double,b){} };std::unordered_map<Key, Val> m2;
m2.emplace(std::piecewise_construct,std::forward_as_tuple(123),      // Key(int)std::forward_as_tuple(3.14)      // Val(double)
);

何时优先使用哪种?

  • 简单场景:插入已有 pair 或者初学者,为了可读性,用 insert 也很直观。

  • 性能敏感或避免临时:当 T 构造/拷贝/移动开销较大时,优先 emplace。

  • 复杂构造:需要传多个参数给 key 或 value 的构造函数时,emplace(尤其配合 piecewise_construct)更加灵活。

相关文章:

  • 量子计算:开启未来科技之门的钥匙
  • 【网络】IP层的重要知识
  • C++笔记-list
  • yolov8复现
  • Redis List 的详细介绍
  • Socket通信的基本概述
  • 视线估计的相关研究
  • PHP 文件上传
  • prism
  • MahApps.Metro:专为 WPF 应用程序设计的 UI 框架
  • jmeter提取返回值到文件
  • Python高级爬虫之JS逆向+安卓逆向1.5节: 控制结构
  • 【问题】一招解决vscode输出和终端不一致的困扰
  • 三菱FX5U设置修改删除口令
  • dispaly: inline-flex 和 display: flex 的区别
  • vscode stm32 variable uint32_t is not a type name 问题修复
  • 代码随想录刷题|Day20(组合总数,组合总数2、分割回文串)
  • Python内置函数---all()
  • 「ollama」安装包
  • 开源语音合成模型SparkTTS使用
  • 马上评|训斥打骂女儿致死,无暴力应是“管教”底线
  • 刘强东坐镇京东一线:管理层培训1800人次,最注重用户体验
  • 刘晓庆被实名举报涉嫌偷税漏税,税务部门启动调查
  • 女孩患异食癖爱吃头发,一年后腹痛入院体内惊现“头发巨石”
  • 赖清德为“临阵脱逃”作准备,国台办:绝不会任“台独”祸首逍遥法外
  • 颜福庆与顾临的争论:1930年代在中国维持一家医学院要花多少钱