一、unordered 系列关联式容器
在 C++98 标准中,STL 提供的关联式容器底层多基于红黑树,查询效率为 O(log₂N)。当数据量极大时,这种比较次数带来的开销不容忽视。C++11 引入了 unordered 系列容器,其底层采用哈希表结构,将平均查找复杂度优化至 O(1)。
虽然接口使用上与 map/set 类似,但内部机制差异巨大。本文重点剖析 unordered_map 和 unordered_set 的原理及实现细节。
1.1 unordered_map 核心特性
unordered_map 是存储键值对的无序容器,允许通过 key 快速索引到对应的 value。其核心特点包括:
- 无序性:元素排列由哈希函数决定,不保证顺序。
- 快速访问:基于哈希表,提供常数级的查找、插入和删除性能。
- 唯一性:Key 必须唯一,重复插入会覆盖旧值。
- 直接访问:支持 operator[] 操作符直接通过 Key 获取或修改 Value。
1.2 接口概览
主要接口涵盖构造、容量控制、迭代器遍历、元素访问(insert/erase/find)以及桶操作。值得注意的是,operator[] 在查找失败时会默认构造一个 value 并插入,这在某些场景下可能产生副作用,建议优先使用 insert 或 find 进行精确控制。
二、底层结构与哈希原理
unordered 系列之所以高效,关键在于其底层的哈希结构。
2.1 哈希概念
理想搜索无需比较即可定位元素。哈希方法通过 hashFunc 建立关键码与存储位置的一一映射关系。插入时计算地址存放,搜索时同样计算地址比对。例如对集合 {1, 7, 6, 4, 5, 9} 取模 capacity,可直接定位存储位置,大幅减少比较次数。
2.2 哈希冲突
不同关键字计算出相同哈希地址的现象称为哈希冲突(Collision)。具有相同哈希地址的不同关键码被称为'同义词'。设计良好的哈希函数能降低冲突概率,但无法完全避免。
2.3 哈希函数设计
常见哈希函数包括:
- 直接定址法:Hash(Key) = A*Key + B,简单但需预知分布。
- 除留余数法:Hash(key) = key % p (p 为质数),最常用且效果较好。
- 平方取中法:适合位数较大且分布未知的情况。
- 折叠法:将关键字分段叠加,适合长关键字。
- 随机数法:适用于长度不等的关键字。
2.4 冲突解决策略
2.4.1 闭散列(开放定址法)
发生冲突时,若表未满,则寻找下一个空位。典型方法是线性探测:从冲突位置向后逐个探查。
伪删除处理:不能物理删除元素,否则影响后续查找。通常标记为 DELETE 状态,逻辑上视为占用但可被新元素覆盖。
template<class K> struct DefaultHashFunc {
size_t operator()(const K& key) { return (size_t)key; }
};
<> <string> {
{
hash = ;
( ch : str) { hash *= ; hash += ch; }
hash;
}
};
open_address {
{ EXIST, EMPTY, DELETE };
< , > {
pair<K, V> _kv;
STATE _state = EMPTY;
};
< , , = DefaultHashFunc<K>>
HashTable {
:
() { _table.(); }
{
((kv.first)) ;
(_n * / _table.() >= ) {
newSize = _table.() * ;
HashTable newHT;
newHT._table.(newSize);
( i = ; i < _table.(); i++) {
(_table[i]._state == EXIST) newHT.(_table[i]._kv);
}
_table.(newHT._table);
}
HashFunc hf;
hashi = (kv.first) % _table.();
(_table[hashi]._state == EXIST) {
++hashi; hashi %= _table.();
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
;
}
{
HashFunc hf;
hashi = (key) % _table.();
(_table[hashi]._state != EMPTY) {
(_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
(HashData< K, V>*) & _table[hashi];
++hashi; hashi %= _table.();
}
;
}
{
HashData< K, V>* ret = (key);
(ret) { ret->_state = DELETE; --_n; ; }
;
}
:
vector<HashData<K, V>> _table;
_n = ;
};
}


