之前对hash一直存在恐惧感,毕竟没用过……最近在一个组件里面自己实现了一个hashtable,感觉也就这么回事;回头看看书上对hashtable的分析,发现是极其的相似。不过,旧版本的C++标准里面并没有hashtable这个东西,而C++11中引入了相关的容器(std::unordered_set, std::unordered_multiset, std::unordered_map, std::unordered_multimap),所以可以直接使用C++11里面的容器了。
1. hashtable结构
SGI STL中hash table使用的是开链法进行的冲突处理,其结构如图所示:
hash table的节点定义如下:
template
struct _Hashtable_node
{
_Hashtable_node* _M_next;
_Val _M_val;
};
2. hashtable迭代器
这里省略了不必要的代码,只需要注意迭代器类型、成员组成以及几个关键的操作即可。
struct _Hashtable_iterator {
typedef _Hashtable_node<_Val> _Node;
typedef forward_iterator_tag iterator_category;
_Node* _M_cur;
_Hashtable* _M_ht;
iterator& operator++();
iterator operator++(int);
};
hashtable的迭代器类型为ForwardIterator,所以只支持operator++操作。
template
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>&
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++()
{
// 从当前的节点开始进行遍历操作
const _Node* __old = _M_cur;
// 取得下一个节点的指针
_M_cur = _M_cur->_M_next;
// 如果为空,表明当前的bucket已经没有节点了,需要指向下一个存在节点的bucket
if (!_M_cur) {
// 取得当前的bucket索引值
size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val);
// 寻找下一个存在节点的bucket
while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size())
_M_cur = _M_ht->_M_buckets[__bucket];
}
return *this;
}
// operator++(int)基于operator++()实现
template
inline _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>
_Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++(int)
{
iterator __tmp = *this;
++*this;
return __tmp;
}
3. hashtable关键实现
3.1 素数
很多书籍上提到最好取一个素数作为hash表格的大小,但是看了下网上似乎有两种观点:一种赞同,另一种说取其他数也可以。不过都认同的一个观点是,m不应该是进制数的幂,比如十进制的时候,m如果是10^n,那么结果总是和原始值的后n位相关的,这样冲突的概率会更大。所以,CLRS上面也提到了m常常选择与2的幂不太接近的质数。在这种情况下,取一个素数总是个不坏的选择。
SGI STL提供了28个素数最为备选方案,__stl_next_prime可以选出一个最接近n且比n要大的素数。
enum { __stl_num_primes = 28 };
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
inline unsigned long __stl_next_prime(unsigned long __n)
{
const unsigned long* __first = __stl_prime_list;
const unsigned long* __last = __stl_prime_list + (int)__stl_num_primes;
const unsigned long* pos = lower_bound(__first, __last, __n);
return pos == __last ? *(__last - 1) : *pos;
}
size_type max_bucket_count() const
{
return __stl_prime_list[(int)__stl_num_primes - 1];
}
3.2 关键源码
template
class hashtable {
public:
typedef _Key key_type;
typedef _Val value_type;
typedef _HashFcn hasher;
typedef _EqualKey key_equal;
hasher hash_funct() const { return _M_hash; }
key_equal key_eq() const { return _M_equals; }
private:
// hash table 节点
typedef _Hashtable_node<_Val> _Node;
private:
hasher _M_hash;
key_equal _M_equals;
_ExtractKey _M_get_key;
// bucket vector : hash数组本身使用vector进行管理
vector<_Node*,_Alloc> _M_buckets;
size_type _M_num_elements;
public:
hashtable(size_type __n,
const _HashFcn& __hf,
const _EqualKey& __eql,
const _ExtractKey& __ext,
const allocator_type& __a = allocator_type())
: __HASH_ALLOC_INIT(__a)
_M_hash(__hf),
_M_equals(__eql),
_M_get_key(__ext),
_M_buckets(__a),
_M_num_elements(0)
{
// 调用_M_initialize_buckets进行初始化操作
_M_initialize_buckets(__n);
}
hashtable(size_type __n,
const _HashFcn& __hf,
const _EqualKey& __eql,
const allocator_type& __a = allocator_type())
: __HASH_ALLOC_INIT(__a)
_M_hash(__hf),
_M_equals(__eql),
_M_get_key(_ExtractKey()),
_M_buckets(__a),
_M_num_elements(0)
{
_M_initialize_buckets(__n);
}
hashtable(const hashtable& __ht)
: __HASH_ALLOC_INIT(__ht.get_allocator())
_M_hash(__ht._M_hash),
_M_equals(__ht._M_equals),
_M_get_key(__ht._M_get_key),
_M_buckets(__ht.get_allocator()),
_M_num_elements(0)
{
_M_copy_from(__ht);
}
~hashtable() { clear(); }
size_type size() const { return _M_num_elements; }
size_type max_size() const { return size_type(-1); }
bool empty() const { return size() == 0; }
// begin() 返回第一个有效的节点,不存在则返回end()
iterator begin()
{
for (size_type __n = 0; __n < _M_buckets.size(); ++__n)
if (_M_buckets[__n])
return iterator(_M_buckets[__n], this);
return end();
}
iterator end() { return iterator(0, this); }
public:
size_type bucket_count() const { return _M_buckets.size(); }
// 返回hash数组中指定bucket下标上冲突链表的长度
size_type elems_in_bucket(size_type __bucket) const
{
size_type __result = 0;
for (_Node* __cur = _M_buckets[__bucket]; __cur; __cur = __cur->_M_next)
__result += 1;
return __result;
}
// 插入操作
pair insert_unique(const value_type& __obj)
{
resize(_M_num_elements + 1);
return insert_unique_noresize(__obj);
}
iterator insert_equal(const value_type& __obj)
{
resize(_M_num_elements + 1);
return insert_equal_noresize(__obj);
}
pair insert_unique_noresize(const value_type& __obj);
iterator insert_equal_noresize(const value_type& __obj);
// 查找指定的key
iterator find(const key_type& __key)
{
// 计算key得到的下标
size_type __n = _M_bkt_num_key(__key);
// 遍历冲突链表
_Node* __first;
for ( __first = _M_buckets[__n];
__first && !_M_equals(_M_get_key(__first->_M_val), __key);
__first = __first->_M_next)
{}
return iterator(__first, this);
}
// 计算具有指定key的节点的个数
size_type count(const key_type& __key) const
{
const size_type __n = _M_bkt_num_key(__key);
size_type __result = 0;
for (const _Node* __cur = _M_buckets[__n]; __cur; __cur = __cur->_M_next)
if (_M_equals(_M_get_key(__cur->_M_val), __key))
++__result;
return __result;
}
private:
// 计算hash表格的大小(取素数表中的合适的素数)
size_type _M_next_size(size_type __n) const
{ return __stl_next_prime(__n); }
// 初始化hash数组
void _M_initialize_buckets(size_type __n)
{
const size_type __n_buckets = _M_next_size(__n);
_M_buckets.reserve(__n_buckets);
_M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
_M_num_elements = 0; // 实际节点个数
}
// 计算下标的几组函数
size_type _M_bkt_num_key(const key_type& __key) const
{
return _M_bkt_num_key(__key, _M_buckets.size());
}
size_type _M_bkt_num(const value_type& __obj) const
{
return _M_bkt_num_key(_M_get_key(__obj));
}
size_type _M_bkt_num_key(const key_type& __key, size_t __n) const
{
return _M_hash(__key) % __n;
}
size_type _M_bkt_num(const value_type& __obj, size_t __n) const
{
return _M_bkt_num_key(_M_get_key(__obj), __n);
}
// 内存管理:分配新节点
_Node* _M_new_node(const value_type& __obj)
{
_Node* __n = _M_get_node();
__n->_M_next = 0;
__STL_TRY {
construct(&__n->_M_val, __obj);
return __n;
}
__STL_UNWIND(_M_put_node(__n));
}
// 内存管理:节点回收
void _M_delete_node(_Node* __n)
{
destroy(&__n->_M_val);
_M_put_node(__n);
}
};
// 插入操作,不允许重复
template
pair::iterator, bool>
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_unique_noresize(const value_type& __obj)
{
const size_type __n = _M_bkt_num(__obj);
_Node* __first = _M_buckets[__n];
// 如果已经存在则直接返回
for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
return pair(iterator(__cur, this), false);
// 插入新节点
_Node* __tmp = _M_new_node(__obj);
__tmp->_M_next = __first;
_M_buckets[__n] = __tmp;
++_M_num_elements;
return pair(iterator(__tmp, this), true);
}
// 插入操作,允许重复
template
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_equal_noresize(const value_type& __obj)
{
const size_type __n = _M_bkt_num(__obj);
_Node* __first = _M_buckets[__n];
// 如果发现同样的key的节点存在,则插入到这个节点之后
for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj))) {
_Node* __tmp = _M_new_node(__obj);
__tmp->_M_next = __cur->_M_next;
__cur->_M_next = __tmp;
++_M_num_elements;
return iterator(__tmp, this);
}
// 否则插入到链表头部
_Node* __tmp = _M_new_node(__obj);
__tmp->_M_next = __first;
_M_buckets[__n] = __tmp;
++_M_num_elements;
return iterator(__tmp, this);
}
// 查找或者插入:找到则直接返回,否则进行插入操作
template
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::reference
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::find_or_insert(const value_type& __obj)
{
resize(_M_num_elements + 1);
size_type __n = _M_bkt_num(__obj);
_Node* __first = _M_buckets[__n];
for (_Node* __cur = __first; __cur; __cur = __cur->_M_next)
if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))
return __cur->_M_val;
_Node* __tmp = _M_new_node(__obj);
__tmp->_M_next = __first;
_M_buckets[__n] = __tmp;
++_M_num_elements;
return __tmp->_M_val;
}
// 删除指定key的节点
template
typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::size_type
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::erase(const key_type& __key)
{
const size_type __n = _M_bkt_num_key(__key);
_Node* __first = _M_buckets[__n];
size_type __erased = 0;
if (__first) {
_Node* __cur = __first;
_Node* __next = __cur->_M_next;
while (__next) {
if (_M_equals(_M_get_key(__next->_M_val), __key)) {
__cur->_M_next = __next->_M_next;
_M_delete_node(__next);
__next = __cur->_M_next;
++__erased;
--_M_num_elements;
}
else {
__cur = __next;
__next = __cur->_M_next;
}
}
if (_M_equals(_M_get_key(__first->_M_val), __key)) {
_M_buckets[__n] = __first->_M_next;
_M_delete_node(__first);
++__erased;
--_M_num_elements;
}
}
return __erased;
}
// 重新调整表格大小
template
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::resize(size_type __num_elements_hint)
{
const size_type __old_n = _M_buckets.size();
// 超过原来表格的大小时才进行调整
if (__num_elements_hint > __old_n) {
// 新的表格大小
const size_type __n = _M_next_size(__num_elements_hint);
// 在边界情况下可能无法调整(没有更大的素数了)
if (__n > __old_n) {
vector<_Node*, _All> __tmp(__n, (_Node*)(0),
_M_buckets.get_allocator());
__STL_TRY {
// 填充新的表格
for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
_Node* __first = _M_buckets[__bucket];
while (__first) {
size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
_M_buckets[__bucket] = __first->_M_next;
__first->_M_next = __tmp[__new_bucket];
__tmp[__new_bucket] = __first;
__first = _M_buckets[__bucket];
}
}
// 通过swap交换
_M_buckets.swap(__tmp);
}
# ifdef __STL_USE_EXCEPTIONS
// 异常处理
catch(...) {
for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
while (__tmp[__bucket]) {
_Node* __next = __tmp[__bucket]->_M_next;
_M_delete_node(__tmp[__bucket]);
__tmp[__bucket] = __next;
}
}
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
}
}
// 清空hash表
template
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::clear()
{
for (size_type __i = 0; __i < _M_buckets.size(); ++__i) {
// 删除每个冲突链表上的所有节点
_Node* __cur = _M_buckets[__i];
while (__cur != 0) {
_Node* __next = __cur->_M_next;
_M_delete_node(__cur);
__cur = __next;
}
_M_buckets[__i] = 0;
}
_M_num_elements = 0;
}
4. unordered_map
C++11标准里面纳入了相关的四个容器:(可以把unordered_map和unordered_multimap当做hashtable来使用)
unordered_map很多接口和SGI的hashtable非常相似。不过我并没有用过hashtable,倒是后来用unordered_map用的多一点。例子很多,这里就不说了。
5. swap
在《Effective C++》讲解异常的时候有提到通过swap的方式确保操作要么成功,要么失败。比如如果要修改某一个地方,可以先通过copy构造一个副本,然后对副本进行修改,最后通过swap操作来实现对原始对象的修改。
这一点在上面分析的resize函数中也有很好的体现:
// 重新调整表格大小
template
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::resize(size_type __num_elements_hint)
{
const size_type __old_n = _M_buckets.size();
if (__num_elements_hint > __old_n) {
const size_type __n = _M_next_size(__num_elements_hint);
if (__n > __old_n) {
vector<_Node*, _All> __tmp(__n, (_Node*)(0),
_M_buckets.get_allocator());
// =============================================================
// 构造临时的hash表tmp
// =============================================================
__STL_TRY {
for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
_Node* __first = _M_buckets[__bucket];
while (__first) {
size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);
_M_buckets[__bucket] = __first->_M_next;
__first->_M_next = __tmp[__new_bucket];
__tmp[__new_bucket] = __first;
__first = _M_buckets[__bucket];
}
}
// =============================================================
// 通过swap交换
// =============================================================
_M_buckets.swap(__tmp);
}
// =============================================================
// 如果出错,则析构tmp中的节点
// =============================================================
catch(...) {
for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
while (__tmp[__bucket]) {
_Node* __next = __tmp[__bucket]->_M_next;
_M_delete_node(__tmp[__bucket]);
__tmp[__bucket] = __next;
}
}
throw;
}
}
}
}