
先以图解的形式,看一下数据是如何映射到哈希表当中的:

可以看到,Key值经过hash函数后,数据一致的并没有分开储存,而是连接到了原来数据之下。因此,让我们来看看链地址法的基础构成部分:
在我们的实现中,哈希表由一个 vector 容器 _tables 组成,每个元素是一个指向 HashNode 的指针。HashNode 是一个简单的结构体,包含一个键值对 _kv 和一个指向下一个节点的指针 _next。这种结构使得我们可以方便地在链表中插入和删除节点。
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
, _next(nullptr)
{
}
};哈希表的构造函数初始化 _tables 为一个大小为质数的空链表数组,并将 _n(存储的有效数据个数)设置为 0。析构函数负责释放所有链表节点的内存,防止内存泄漏。
HashTable(size_t n = __stl_next_prime(0))
:_tables(n, nullptr)
, _n(0)
{
}
~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;
}
}插入操作的目标是将一个新的键值对添加到哈希表中。以下是插入操作的详细步骤:
Find 函数检查待插入的键是否已经存在于哈希表中。如果键已存在,说明该键值对无需再次插入,直接返回 false。Hash 对键进行哈希运算,得到一个哈希值。然后,将该哈希值对哈希表的大小取模,得到该键值对在哈希表中的槽位索引。_n 等于哈希表的大小,说明哈希表已满,需要进行扩容操作。扩容的具体做法是创建一个新的哈希表,其大小为当前哈希表大小的下一个质数。接着,遍历旧哈希表中的所有链表,将每个链表中的节点重新映射到新哈希表中对应的槽位。HashNode 节点,将待插入的键值对存储在该节点中。然后,采用头插法将新节点插入到对应槽位的链表头部。头插法的好处是操作简单且时间复杂度低,无需遍历整个链表。_n 加 1。true。bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
Hash hs;
// 负载因子到1扩容
if (_n == _tables.size())
{
// 扩容
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
// 遍历旧表,将旧表的数据全部重新映射到新表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// cur头插到新表
size_t hashi = hs(cur->_kv.first) % newtables.size();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
size_t hashi = hs(kv.first) % _tables.size();
// 头插
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}查找操作的目的是在哈希表中查找一个特定的键,并返回与该键关联的值。以下是查找操作的详细步骤:
Hash 对待查找的键进行哈希运算,得到一个哈希值。然后,将该哈希值对哈希表的大小取模,得到该键在哈希表中的槽位索引。nullptr,表示该键不存在于哈希表中。Node* Find(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
return cur;
cur = cur->_next;
}
return nullptr;
}== 运算符,以便正确比较键的相等性。删除操作的目标是从哈希表中移除一个特定的键值对。以下是删除操作的详细步骤:
Hash 对待删除的键进行哈希运算,得到一个哈希值。然后,将该哈希值对哈希表的大小取模,得到该键在哈希表中的槽位索引。_next 指针指向当前节点的下一个节点。delete 释放该节点占用的内存。_n 减 1。true,如果未找到匹配的键,则返回 false。bool Erase(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
--_n;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}namespace hash_bucket
{
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
, _next(nullptr)
{
}
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable(size_t n = __stl_next_prime(0))
:_tables(n, nullptr)
, _n(0)
{
}
~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;
}
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
Hash hs;
// 负载因子到1扩容
if (_n == _tables.size())
{
// 扩容
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
// 遍历旧表,将旧表的数据全部重新映射到新表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// cur头插到新表
size_t hashi = hs(cur->_kv.first) % newtables.size();
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
size_t hashi = hs(kv.first) % _tables.size();
// 头插
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
return cur;
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
--_n;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables;
size_t _n; // 实际存储有效数据个数
};
}unordered_xxx底层都是哈希表,通过调用哈希表的接口,进而实现。在这部分,基本与map和set自我实现部分类似,这里直接给出结构。
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;
}; 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);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert({ key, V() });
return ret.first->second;
}
private:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};由于上层unordered_xxx向哈希表传来的数据并不同,unordered_set是key而unordered_map是pair类型,所以在底层,哈希表处,我们统一用data来同时处理这两种数据。
下面是对节点处的修改:
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{
}
};针对各类操作,我们需要统一将数据改为data。 在这里还要注意的一点是,在上层unordered_xxx处,我们实现了KeyOfT函数,并传给了模板参数,也就是这里的kot。这是应为,当数据data是pair类型时,我们是不能够直接取出它的key值的,因此,让unordered_set和unordered_map各自实现自己的函数,在底层我们只需统一调用kot函数即可。 下面是对插入,寻找,删除函数的修改:
pair<Iterator, bool> Insert(const T& data)
{
KeyOfT kot;
Iterator it = Find(kot(data));
if (it != End())
return { it, false };
Hash hs;
// 负载因子到1扩容
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
// 遍历旧表,将旧表的数据全部重新映射到新表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// cur头插到新表
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 { Iterator(newnode, this), true };
}
Iterator Find(const K& key)
{
Hash hs;
KeyOfT kot;
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)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
KeyOfT kot;
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;
}
--_n;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}注意:
map⽀持[]

可以看到insert的返回值是一个pair类型的结构。first指向的是一个迭代器,就是所插入值的迭代器,第二个second是bool类型,用来判断是否插入成功。
迭代器的实现,主要有++, * , -> , != , == 操作。在这里我们主要说明++操作。其余只将实现列出。
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])
{
_node = _pht->_tables[hashi];
break;
}
++hashi;
}
if (hashi == _pht->_tables.size())
{
// 所有桶都走完了,置为end()
_node = nullptr;
}
}
return *this;
}如果当前节点 _node 的 _next 指针不为空,说明当前链表还有后续节点,直接将 _node 指向下一个节点:
if (_node->_next)
{
_node = _node->_next;
}如果当前节点 _node 的 _next 指针为空,说明当前链表已经遍历完毕,需要找到下一个非空的链表头节点。以下是实现步骤:
获取当前节点的哈希值:通过 KeyOfT 和 Hash 获取当前节点数据的哈希值,并计算其在哈希表中的位置:
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();这里,kot 是一个 KeyOfT 类型的对象,用于从数据中提取键值;hs 是一个 Hash 类型的对象,用于计算键值的哈希值。
寻找下一个非空链表:从当前哈希值的下一个位置开始,逐个检查哈希表中的槽位,直到找到一个非空的链表:
hashi++;
while (hashi < _pht->_tables.size())
{
if (_pht->_tables[hashi])
{
_node = _pht->_tables[hashi];
break;
}
++hashi;
}如果找到非空链表,将 _node 指向该链表的头节点。
处理所有桶都已遍历完的情况:如果遍历完所有槽位都没有找到非空链表,说明已经到达哈希表的末尾,将 _node 置为 nullptr,表示迭代器到达 end():
if (hashi == _pht->_tables.size())
{
_node = nullptr;
}最后,返回当前迭代器对象的引用,以便支持链式操作:
return *this;template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
Node* _node;
const HT* _pht;
HTIterator(Node* node, const HT* pht)
:_node(node)
, _pht(pht)
{
}
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])
{
_node = _pht->_tables[hashi];
break;
}
++hashi;
}
if (hashi == _pht->_tables.size())
{
// 所有桶都走完了,置为end()
_node = nullptr;
}
}
return *this;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;
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);
}
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
return ConstIterator(_tables[i], this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}这段代码实现了哈希表的 Begin 和 End 方法,分别用于获取指向哈希表第一个元素的迭代器和指向哈希表末尾的迭代器。这些方法对于遍历哈希表中的所有元素非常重要。下面是对这段代码的详细解析:
首先,定义了两种迭代器类型:
typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;Iterator:用于非常量哈希表的迭代器,允许修改元素。ConstIterator:用于常量哈希表的迭代器,不允许修改元素。Begin 方法Begin 方法返回一个指向哈希表第一个元素的迭代器。如果哈希表为空,则返回 End。
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();
}_n(实际存储的有效数据个数)为 0,说明哈希表为空,直接返回 End。_tables[i] 是否非空。End:
_n),返回 End。End 方法End 方法返回一个指向哈希表末尾的迭代器。这个迭代器通常用于表示迭代的结束。
Iterator End()
{
return Iterator(nullptr, this);
}_node 指针为 nullptr,表示迭代结束。_pht 指向当前哈希表对象,用于在迭代器的递增操作中访问哈希表的内部结构。Begin 和 End为了支持常量哈希表的遍历,还提供了常量版本的 Begin 和 End 方法:
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
return ConstIterator(_tables[i], this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}ConstIterator。ConstIterator 保证了对哈希表中元素的只读访问,符合常量方法的要求。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 Ref, class Ptr, class KeyOfT, class Hash>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
Node* _node;
const HT* _pht;
HTIterator(Node* node, const HT* pht)
:_node(node)
, _pht(pht)
{
}
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])
{
_node = _pht->_tables[hashi];
break;
}
++hashi;
}
if (hashi == _pht->_tables.size())
{
// 所有桶都走完了,置为end()
_node = nullptr;
}
}
return *this;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
// 友元声明
template<class K, class T, class Ref, class Ptr, 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;
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);
}
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
return ConstIterator(_tables[i], this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
HashTable(size_t n = __stl_next_prime(0))
:_tables(n, nullptr)
, _n(0)
{
}
~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;
}
}
pair<Iterator, bool> Insert(const T& data)
{
KeyOfT kot;
Iterator it = Find(kot(data));
if (it != End())
return { it, false };
Hash hs;
// 负载因子到1扩容
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
// 遍历旧表,将旧表的数据全部重新映射到新表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// cur头插到新表
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 { Iterator(newnode, this), true };
}
Iterator Find(const K& key)
{
Hash hs;
KeyOfT kot;
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)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
KeyOfT kot;
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;
}
--_n;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables;
size_t _n; // 实际存储有效数据个数
};
}