定义:std::unordered_map
特点:
无序:元素按哈希值分布在若干“桶”(bucket)中,不保证遍历顺序。
唯一键:同一个键最多只能出现一次;如果插入已存在的键,默认不覆盖(可用 operator[] 或 at 修改)。
可自定义:支持自定义哈希函数和键相等比较器,也可指定内存分配器。
哈希桶结构
容器内部维护一组“桶”,每个桶是一个链表(或其他冲突解决结构)。
元素根据 Hash(key) % bucket_count 落入对应桶中。
装载因子(load factor)
定义为 size() / bucket_count,表示平均每个桶的元素数。
当装载因子超过 max_load_factor() 时,容器会自动进行 rehash(扩容并重新分配桶)。
再哈希(rehash)
调用 rehash(n) 会将桶数调整为 ≥ n,并将所有元素重新分布。
reserve(n) 则是以元素数 n 为基准,确保 bucket_count ≥ n / max_load_factor()。
操作 | 平均复杂度 | 最坏情况复杂度 | 备注 |
---|---|---|---|
find / operator[] | O(1) | O(n) | 取决于哈希冲突;若所有元素落在同一桶,退化为链表查找 |
insert / emplace | O(1) | O(n) | 同上,且可能触发一次 rehash(O(n)) |
erase(key) | O(1) | O(n) | 同上 |
clear | O(n) | O(n) | |
遍历(iteration) | O(n + bucket_count) | O(n + bucket_count) | 需跳过空桶 |
空间开销:
哈希函数好坏:
// 构造与析构
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;
#include
#include
#include
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;
}
当键类型为自定义结构体时,需要提供 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;
避免频繁 rehash
迭代顺序不稳定
哈希攻击
线程安全
内存占用
需要
快速查找/插入/删除 且无需有序遍历时,首选 std::unordered_map。
容器大小可预估,且希望通过 reserve() 控制 rehash 时机。
不需要
保持元素有序或按键排序输出时,应选用 std::map。
需要对容器做范围算法(如二分查找)或有序区间操作时。
功能
都是在 std::unordered_map 中插入元素(键值对)。
若指定键已存在,插入操作不生效,都会返回相同的迭代器和 false。
返回值
特性 | insert | emplace |
---|---|---|
接口签名 | 多重重载,典型如 insert(const value_type&)、insert(value_type&&)、insert(initializer_list |
template |
参数 | 需要先构造好 value_type(即 pair |
直接将参数完美转发(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)更加灵活。