std::unordered_map(C++)

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)更加灵活。

### C++ 中 `std::unordered_map` 的使用方法 #### 创建和初始化 `std::unordered_map` 可以创建一个空的 `std::unordered_map` 或者通过初始列表来填充它。 ```cpp #include <iostream> #include <unordered_map> int main() { // 创建一个空的 unordered_map std::unordered_map<int, std::string> map; // 插入键值对 map.insert({1, "one"}); map.emplace(2, "two"); // 使用初始列表创建并赋初值 std::unordered_map<int, std::string> initMap = {{3, "three"}, {4, "four"}}; } ``` #### 访问元素 可以通过下标操作符访问已存在的键对应的值;如果该键不存在,则会自动插入一个新的默认构造的值。 ```cpp // 假设已经有一个名为 myMapstd::unordered_map<int, double> double value = myMap[key]; // 如果 key 存在则返回对应值,否则插入新项并将 value 设为0.0[^1] ``` #### 迭代遍历 迭代器用于顺序访问容器内的所有元素。注意这里的顺序是没有定义过的,因为哈希表不保持任何特定顺序。 ```cpp for (auto& pair : map) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } ``` #### 查找与删除 提供了成员函数如 `find()` 来定位指定键的位置以及 `erase()` 函数移除单个或多个条目。 ```cpp if (map.find(key) != map.end()) { // 键存在时的操作... } // 删除某个具体的键 map.erase(specificKey); // 清空整个映射 map.clear(); ``` #### 处理冲突 虽然内部实现细节通常由标准库处理,但在某些情况下可能需要考虑负载因子(load factor),即桶的数量相对于存储项目数的比例。当这个比例过高时可能会降低效率,因此有时有必要调整最大负载因子以优化性能。 ```cpp float currentLoadFactor = map.load_factor(); // 获取当前负载因子 size_t bucketCount = map.bucket_count(); // 获取桶数量 map.max_load_factor(0.75f); // 设置新的最大负载因子 map.rehash(bucketCount * 2); // 请求至少两倍于现有桶数的新桶数目 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值