- 0 相关文件
- 1 函数对象hash
- 1.1 字符串求哈希
- 1.2 对数值求哈希
- 2 hashtable
- 2.1 hashtable的数据结构
- 2.2 hashtable的增删查
- 2.3 hashtable中的迭代器
- 2.4 hashtable的扩容策略
- 2.5 hashtable如何实现hash_set/hash_map/hash_multiset/hash_multimap
0 相关文件
stl_hash_fun.h stl_hashtable.h hashtable.h
1 函数对象hash
hash
是一个函数对象,它对各种不同类型的值进行哈希,并返回size_t
类型的哈希值。在x64平台下,size_t
的长度为8 Byte。
template <class _Key> struct hash { };
1.1 字符串求哈希
这里的哈希算法还是非常简单的。假设字符串长度为n
, 最终哈希值为 在
的基础上根据size_t的长度做截断。
__STL_TEMPLATE_NULL struct hash<char*> { size_t operator()(const char* __s) const { return __stl_hash_string(__s); } } inline size_t __stl_hash_string(const char* __s) { unsigned long __h = 0; for ( ; *__s; ++__s) __h = 5*__h + *__s; return size_t(__h); }
1.2 对数值求哈希
直接将数值强转成size_t
即可
__STL_TEMPLATE_NULL struct hash<char> { size_t operator()(char __x) const { return __x; } } __STL_TEMPLATE_NULL struct hash<short> { size_t operator()(short __x) const { return __x; } } __STL_TEMPLATE_NULL struct hash<int> { size_t operator()(int __x) const { return __x; } } __STL_TEMPLATE_NULL struct hash<long> { size_t operator()(long __x) const { return __x; } };
2 hashtable
2.1 hashtable的数据结构
STL中hashtable使用桶+链表实现。
hashtable
中模板参数众多,其中
_Val
, 链表中每个节点中包含的数据类型。
_Key
, 哈希表的key
的类型
_HashFcn
:用于将key
映射到桶中对应的slot
。已知_Key
值,桶中bucket_slot = _HashFcn(_Key) % buckets_num
,其中buckets_num
表示桶的大小。
_ExtractKey
: 用于将_Key
值从_Value
值中抽取出来。已知_Value
值,key=_ExtractKey(value)
。注意:这里切莫将key
和value
等同于把std::map
中的key
和value
,对应关系稍后见分晓。
_EqualKey
: 用于判断两个_Key值是否相等的函数对象(并非所有key的类型都定义了比较函数)
_Alloc
: 链表节点的内存分配器
template <class _Val, class _Key, class _HashFcn, class _ExtractKey, class _EqualKey, class _Alloc> class hashtable { public: typedef _Key key_type; typedef _Val value_type; typedef _HashFcn hasher; typedef _EqualKey key_equal; ...
桶的实现如下,它由一个数组组成。数组中元素类型为指向_Hashtable_node<_Val>
结构的指针,数组中每个元素即一个单向链表的header
... private: typedef _Hashtable_node<_Val> _Node; vector<_Node*,_Alloc> _M_buckets; ... template <class _Val> struct _Hashtable_node { _Hashtable_node* _M_next; _Val _M_val; };
2.2 hashtable的增删查
查找实现如下:
- 首先使用
_HashFcn
类型的_M_hash
函数对象对__key
求哈希值,再对桶的大小取模,得到bucket slot位置
- 根据bucket slot得到链表,从头到尾遍历该链表,对于每个节点,使用
_ExtractKey
类型的_M_get_key
函数对象从节点中取出key值,再使用_EqualKey
类型的函数对象_M_equals
判断当前key是否等于__key
。如果等于,则返回当前位置对应的迭代器
iterator find(const key_type& __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); }
插入(此为不允许重复版本,允许重复版本留待读者自行分析)实现如下:
- 首先从
__obj
中分离出__key
, 根据__key
找到bucket slot位置,并得到链表头指针
- 遍历链表,如果发现当前节点的key值等于
__key
,提前返回; 否则创建新节点,值为__obj
,插入到链表头部
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All> pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::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, bool>(iterator(__cur, this), false); _Node* __tmp = _M_new_node(__obj); __tmp->_M_next = __first; _M_buckets[__n] = __tmp; ++_M_num_elements; return pair<iterator, bool>(iterator(__tmp, this), true); }
删除实现如下:
- 首先根据
__key
值找到bucket slot位置,并得到链表头指针
- 从头到尾遍历链表,如果发现当前节点的key值等于
__key
, 则删除之
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All> 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; }
2.3 hashtable中的迭代器
hashtable中迭代器由两个指针组成,一个指向hashtable
,一个指向链表节点。
... public: typedef _Hashtable_iterator<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc> iterator; ... template <class _Val, class _Key, class _HashFcn, class _ExtractKey, class _EqualKey, class _Alloc> struct _Hashtable_iterator { ... _Node* _M_cur; _Hashtable* _M_ht; ...
hashtable迭代器属于前向迭代器,即只能向正向前进无法反向后退(想想为啥无法向后退?)
_Hashtable_iterator::operator++
用于迭代器递进,其实现如下:
- 如果迭代器中
_M_cur
指针指向的节点在链表尾端,则更新_M_cur
指向下一个bucket slot中链表的头部。
- 如果迭代器中
_M_cur
指针指向的节点不在链表尾端,则_M_curr = _M_cur->_M_next
template <class _Val, class _Key, class _HF, class _ExK, class _EqK, class _All> _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; if (!_M_cur) { size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val); while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size()) _M_cur = _M_ht->_M_buckets[__bucket]; } return *this; }
2.4 hashtable的扩容策略
每次insert数据时,判断当前元素数量是否大于桶的大小,如果是,则触发哈希表的扩容策略:
- 首先创建更大的新桶
- 将老桶中的所有数据复制到新桶。老桶中数据在新桶上的bucket slot位置需要重新分布。
- 在新桶上插入元素
注意:这样做容易造成性能不稳定,如果insert
正好触发了扩容,则本次操作执行时间将远大于没有扩容的情况
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All> 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]; } } _M_buckets.swap(__tmp); } }
2.5 hashtable如何实现hash_set/hash_map/hash_multiset/hash_multimap
首先,对于hash_set
或hash_multiset
:
_HashFcn
缺省为hash
函数对象(见1)
_EqualKey
缺省为equal_to<_Value>
_Key
和_Value
类型相同,因为set中只有key, 没有value。
_ExtractKey
为_Identify<_Value>``, 即
_Value值和
_Key`值相等
hash_set
和hash_multiset
的定义如下, hash_set
和hash_multiset
唯一的区别在于是否容忍重复key的存在
template <class _Value, class _HashFcn __STL_DEPENDENT_DEFAULT_TMPL(hash<_Value>), class _EqualKey __STL_DEPENDENT_DEFAULT_TMPL(equal_to<_Value>), class _Alloc = __STL_DEFAULT_ALLOCATOR(_Value) > class hash_set; template <class _Value, class _HashFcn __STL_DEPENDENT_DEFAULT_TMPL(hash<_Value>), class _EqualKey __STL_DEPENDENT_DEFAULT_TMPL(equal_to<_Value>), class _Alloc = __STL_DEFAULT_ALLOCATOR(_Value) > class hash_multiset
对于hash_map
, hash_multimap
:
_HashFcn
默认为hash
函数对象
_EqualKey
默认为equal_to<_Key>
,_Value
为pair<const _Key, Tp>
。已知_Value
值,可通过_Select1st<pair<const _Key,_Tp> >
得到对应的_Key
值
同理,hash_map
和hash_multimap
的区别在于是否容忍重复key的存在。
template <class _Key, class _Tp, class _HashFcn __STL_DEPENDENT_DEFAULT_TMPL(hash<_Key>), class _EqualKey __STL_DEPENDENT_DEFAULT_TMPL(equal_to<_Key>), class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) > class hash_map; template <class _Key, class _Tp, class _HashFcn __STL_DEPENDENT_DEFAULT_TMPL(hash<_Key>), class _EqualKey __STL_DEPENDENT_DEFAULT_TMPL(equal_to<_Key>), class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) > class hash_multimap;