目录
-
- [一、 容器简介](#一、 容器简介)
- [二、 核心接口与使用示例](#二、 核心接口与使用示例)
-
- [1. 容量与基础查询](#1. 容量与基础查询)
- [2. 元素查找](#2. 元素查找)
- [3. operator\[\] 的特殊机制](#3. operator[] 的特殊机制)
- [三、 面试题实战](#三、 面试题实战)
-
- [实战 1:](#实战 1:)
- [实战 2:](#实战 2:)
- [四、 哈希表底层原理](#四、 哈希表底层原理)
- [五、 底层完整实现](#五、 底层完整实现)
-
- [1. 基础组件与哈希函数](#1. 基础组件与哈希函数)
- [2. 哈希桶与迭代器实现](#2. 哈希桶与迭代器实现)
- [3. 封装 unordered_set](#3. 封装 unordered_set)
- [4. 封装 unordered_map](#4. 封装 unordered_map)
- [六、 扩展应用](#六、 扩展应用)
- [七、 总结](#七、 总结)
一、 容器简介
在 C++98 中,STL 提供了底层为红黑树结构的关联式容器,查询效率为 O ( log 2 N ) O(\log_2 N) O(log2N)。为了在海量数据下实现 O ( 1 ) O(1) O(1) 的常数级查找效率,C++11 引入了 4 个 unordered 系列关联式容器。

- 它是存储
<key, value>键值对的关联式容器,允许通过 keys 快速索引到对应的 value。 - 在内部,容器没有对键值对按照任何特定的顺序排序。
- 容器将相同哈希值的键值对放在相同的桶中,以此实现极致的查找速度。
- 由于底层单链表结构限制,它的迭代器至少是前向迭代器(不支持反向遍历)。

- 它是仅存储唯一关键码(Key)的关联式容器,是处理海量数据去重与极速存在性校验的绝佳利器。
- 与
unordered_map机制相同,其内部元素同样无序,依靠哈希函数将关键码映射并存储到对应的哈希桶中。 - 为了防止底层哈希映射位置被破坏,容器中的元素不能被修改(其迭代器本质上是
const迭代器)。 - 同样仅支持单向迭代,其插入、查找、删除的平均时间复杂度均能达到常数级别 O ( 1 ) O(1) O(1)。
二、 核心接口与使用示例
1. 容量与基础查询
empty():检测容器是否为空。size():获取容器的有效元素个数。
cpp
unordered_map<string, int> dict;
dict.insert({"apple", 1});
bool isEmpty = dict.empty(); // 返回 false
size_t count = dict.size(); // 返回 1
2. 元素查找
find(const K& key):返回 key 在哈希桶中的位置迭代器,若未找到则返回end()。count(const K& key):返回关键码为 key 的键值对个数。因容器中 key 不可重复,故返回值最大为 1,常用于快速判断元素是否存在。
cpp
unordered_set<int> us = {1, 2, 3};
// 使用 find 查找
auto it = us.find(2);
if (it != us.end()) {
cout << "找到了: " << *it << endl;
}
// 使用 count 判断存在性
if (us.count(4) == 0) {
cout << "元素 4 不存在" << endl;
}
3. operator\[\] 的特殊机制
unordered_map 提供了 operator[],其实际调用了底层的插入操作。用参数 key 与默认值构造键值对进行插入:
- 若 key 不在容器中,插入成功并返回默认值的引用。
- 若 key 已存在,插入失败,但会返回原来 key 对应的 value 引用。
cpp
unordered_map<string, string> dict;
dict["insert"] = "插入"; // key不存在,插入 {"insert", ""},随后将其 value 修改为 "插入"
dict["insert"] = "覆盖"; // key已存在,直接返回 value 引用并修改为 "覆盖"
三、 面试题实战
实战 1:
给定一个数组,找出一个出现了特定次数的元素。利用 unordered_map 统计频次。
cpp
class Solution {
public:
int repeatedNTimes(vector<int>& A) {
size_t N = A.size() / 2;
unordered_map<int, int> m;
for (auto e : A) {
m[e]++; // 元素不存在则初始化为0后加1,存在则直接加1
}
for (auto& e : m) {
if (e.second == N) return e.first;
}
return -1;
}
};
实战 2:
求两个数组的交集并去重。利用 unordered_set 实现去重与快速匹配。
cpp
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> s1;
for (auto e : nums1) s1.insert(e);
unordered_set<int> s2;
for (auto e : nums2) s2.insert(e);
vector<int> vRet;
for (auto e : s1) {
if (s2.find(e) != s2.end()) {
vRet.push_back(e);
}
}
return vRet;
}
};
四、 哈希表底层原理
哈希表通过哈希函数将元素的关键码映射为存储位置。常见的哈希函数有除留余数法:Hash(key) = key % capacity。
当不同关键字计算出相同的哈希地址时,即发生哈希冲突。解决冲突的方法有两种:
- 闭散列(开放定址法):寻找下一个空位置存放冲突元素(如线性探测)。其载荷因子必须严格限制在 0.7 - 0.8 以下,容易产生数据堆积,空间利用率低。
- 开散列(链地址法 / 哈希桶):将哈希地址相同的元素归于同一个桶,通过单链表链接。STL 主要采用开散列,其载荷因子可以达到 1.0,空间利用率和查找效率更优。
五、 底层完整实现
以下为基于开散列(哈希桶)完整封装 unordered_map 和 unordered_set 的核心代码。
1. 基础组件与哈希函数
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<class K>
struct HashFunc {
size_t operator()(const K& key) { return (size_t)key; }
};
// 针对 string 的哈希特化 (BKDR Hash)
template<>
struct HashFunc<string> {
size_t operator()(const string& key) {
size_t hash = 0;
for (auto e : key) {
hash *= 31;
hash += e;
}
return hash;
}
};
2. 哈希桶与迭代器实现
cpp
namespace hash_bucket
{
template<class T>
struct HashNode {
T _data;
HashNode<T>* _next;
HashNode(const T& data) :_data(data), _next(nullptr) {}
};
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
// 迭代器实现
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
struct HTIterator {
typedef HashNode<T> Node;
typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;
Node* _node;
const HashTable<K, T, KeyOfT, Hash>* _pht;
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
:_node(node), _pht(pht) {}
Ref operator*() { return _node->_data; }
Ptr operator->() { return &_node->_data; }
bool operator!=(const Self& s) { return _node != s._node; }
Self& operator++() {
if (_node->_next) {
_node = _node->_next;
} else {
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
++hashi;
while (hashi < _pht->_tables.size()) {
if (_pht->_tables[hashi]) break;
++hashi;
}
if (hashi == _pht->_tables.size()) _node = nullptr;
else _node = _pht->_tables[hashi];
}
return *this;
}
};
// 哈希表主体
template<class K, class T, class KeyOfT, class Hash>
class HashTable {
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
friend struct HTIterator;
typedef HashNode<T> Node;
public:
typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;
HashTable() { _tables.resize(10, nullptr); }
~HashTable() {
for (size_t i = 0; i < _tables.size(); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
Iterator Begin() {
if (_n == 0) return End();
for (size_t i = 0; i < _tables.size(); i++) {
if (_tables[i]) return Iterator(_tables[i], this);
}
return End();
}
Iterator End() { return Iterator(nullptr, this); }
pair<Iterator, bool> Insert(const T& data) {
KeyOfT kot;
Iterator it = Find(kot(data));
if (it != End()) return make_pair(it, false);
Hash hs;
if (_n == _tables.size()) {
vector<Node*> newtables(_tables.size() * 2, nullptr);
for (size_t i = 0; i < _tables.size(); i++) {
Node* cur = _tables[i];
while (cur) {
Node* next = cur->_next;
size_t hashi = hs(kot(cur->_data)) % newtables.size();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
size_t hashi = hs(kot(data)) % _tables.size();
Node* newnode = new Node(data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return make_pair(Iterator(newnode, this), true);
}
Iterator Find(const K& key) {
KeyOfT kot;
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur) {
if (kot(cur->_data) == key) return Iterator(cur, this);
cur = cur->_next;
}
return End();
}
bool Erase(const K& key) {
KeyOfT kot;
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur) {
if (kot(cur->_data) == key) {
if (prev == nullptr) _tables[hashi] = cur->_next;
else prev->_next = cur->_next;
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables;
size_t _n = 0;
};
}
3. 封装 unordered_set
cpp
namespace bit
{
template<class K, class Hash = HashFunc<K>>
class unordered_set {
struct SetKeyOfT {
const K& operator()(const K& key) { return key; }
};
public:
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin() { return _ht.Begin(); }
iterator end() { return _ht.End(); }
const_iterator begin() const { return _ht.Begin(); }
const_iterator end() const { return _ht.End(); }
pair<iterator, bool> insert(const K& key) { return _ht.Insert(key); }
iterator Find(const K& key) { return _ht.Find(key); }
bool Erase(const K& key) { return _ht.Erase(key); }
private:
hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};
}
4. 封装 unordered_map
cpp
namespace bit
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map {
struct MapKeyOfT {
const K& operator()(const pair<K, V>& kv) { return kv.first; }
};
public:
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin() { return _ht.Begin(); }
iterator end() { return _ht.End(); }
const_iterator begin() const { return _ht.Begin(); }
const_iterator end() const { return _ht.End(); }
pair<iterator, bool> insert(const pair<K, V>& kv) { return _ht.Insert(kv); }
V& operator[](const K& key) {
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
iterator Find(const K& key) { return _ht.Find(key); }
bool Erase(const K& key) { return _ht.Erase(key); }
private:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
}
六、 扩展应用
在海量数据处理场景下,常规哈希表会面临内存不足的问题。此时可以使用哈希思想的延伸结构:
- 位图 (BitMap):使用二进制比特位代表数据是否存在,适用于海量整型数据的快速查找与去重。
- 布隆过滤器 (Bloom Filter):将哈希函数与位图结合,通过多个哈希函数将一个数据映射到位图中。适用于容忍一定误判率的海量字符串查重过滤场景,空间优势极大。
七、 总结
- 若业务场景需要数据保持特定顺序输出,应选择基于红黑树的
map/set。 - 若核心需求为极限速度的增删查改,优先选用
unordered_map/unordered_set。 - STL 的底层通过仿函数提取器(
KeyOfT)实现了HashTable泛型代码的复用,理解这一点有助于掌握 C++ 的泛型编程逻辑。 - 在使用
operator[]时需明确其底层包含插入逻辑,仅查询判断时应优先使用find()或count()。