以下内容源于MSVC版本的源码分析,花了两天空闲研究源码…
template<class _Kty,
class _Hasher = hash<_Kty>,
class _Keyeq = equal_to<_Kty>,
class _Alloc = allocator<_Kty>>
class unordered_set
: public _Hash<_Uset_traits<_Kty,
_Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, false>>
{ // hash table of key values, unique keys
}
unordered_multiset(const unordered_multiset& _Right);
explicit unordered_multiset(size_type _Buckets);
unordered_multiset(size_type _Buckets, const hasher& _Hasharg);
/*
... 等等, 不一一列举
*/
unordered_set提供了28个构造函数,主要构造这几个部分:
对于MSVC版本的哈希表,存储过程及原理如下:
_Traits _Traitsobj; // traits to customize behavior
_Mylist _List; // list of elements, must initialize before _Vec
_Myvec _Vec; // vector of list iterators, begin() then end()-1
size_type _Mask; // the key mask
size_type _Maxidx; // current maximum key value
存储结构和冲突解决
哈希映射
index = hash & _Mask
0x0000...11111
,也就是取哈希值的低位inline size_t _Fnv1a_append_bytes(size_t _Val,
const unsigned char * const _First, const size_t _Count) noexcept
{ // accumulate range [_First, _First + _Count) into partial FNV-1a hash _Val
for (size_t _Idx = 0; _Idx < _Count; ++_Idx)
{
_Val ^= static_cast<size_t>(_First[_Idx]);
_Val *= _FNV_prime;
}
return (_Val);
}
/*
如果在 WIN64环境:
_Val: 14695981039346656037ULL
_FNV_prime: 1099511628211ULL
*/
初始容量与扩容
简述一遍过程:
index = hash & (buckets - 1)
获取此键值对应存储到的桶编号index
编号桶的元素的链表起止迭代器,同时调整链表顺序,确保对应相同编号桶的元素彼此相邻rehash
由于unordered_set是public继承于_Hash,所以基类的接口也被暴露给外界。
关于桶的接口
size_type bucket_count(); // 获取桶的数量
size_type max_bucket_count(); // 获取桶最大数量
size_type bucket(const key_type& _Keyval); // 获取元素对应的桶编号
size_type bucket_size(size_type _Bucket); // 获取编号_Bucket桶的元素个数
local_iterator begin(size_type _Bucket); // 获取编号_Bucket桶元素的起始迭代器
local_iterator end(size_type _Bucket); // 获取编号_Bucket桶元素的终止迭代器
关于插入删除元素
iterator emplace(_Valty&&... _Val);
size_type erase(const key_type& _Keyval);
这个原理也比较简单,不说了,就是list的删除插入操作。
关于list的接口
iterator begin(); // list起始迭代器
iterator end(); // list终止迭代器
size_type size(); // list大小,即所存元素个数
bool empty(); // 判空
关于扩容与hash的接口
float load_factor(); // 获取负载因子
void rehash(size_type _Buckets); // 调整桶数目,再哈希
void reserve(size_type _Maxcount); // 预留桶数目
hasher hash_function(); // 获取 hash函数
关于查找等算法
iterator find(const key_type& _Keyval); // 查找和 _Keyval相匹配的节点, 返回最左端的迭代器
size_type count(const key_type& _Keyval); // 查找和 _Keyval相匹配的节点个数
iterator lower_bound(const key_type& _Keyval);
iterator upper_bound(const key_type& _Keyval);
_Pairii equal_range(const key_type& _Keyval); // 查找和 _Keyval相匹配的节点迭代器范围
由于对应同一个编号桶的元素彼此相邻,且key相等彼此相邻,所以查找的时候是根据桶的起止迭代器进行顺序遍历查找
而对于unordered_multiset容器,和unordered_set几乎一模一样
template<class _Kty,
class _Hasher = hash<_Kty>,
class _Keyeq = equal_to<_Kty>,
class _Alloc = allocator<_Kty>>
class unordered_multiset
: public _Hash<_Uset_traits<_Kty,
_Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, true>> /* 这里改为 true,表示可以重复键值 */
{
}
简单验证上述的_Hash过程
unordered_multiset<int> st;
st.reserve(32);
cout << "*********************" << endl;
list<int> v{1, 34, 67, 0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280,
80, 67, 260, 80};
for (int i : v) {
std::printf("%d的桶编号为:%d\n", i, st.bucket(i));
st.emplace(i);
}
cout << "st 新桶数: " << st.bucket_count() << endl;
cout << "st 新负载因子: " << st.load_factor() << endl;
cout << "st 新最大负载因子: " << st.max_load_factor() << endl;
cout << "*********************" << endl;
for (auto it = st.begin(); it != st.end(); ++it) {
std::cout << *it << " ";
}
/*
result:
1的桶编号为:4
34的桶编号为:23
67的桶编号为:6
0的桶编号为:21
20的桶编号为:1
40的桶编号为:29
60的桶编号为:9
80的桶编号为:5
100的桶编号为:17
120的桶编号为:13
140的桶编号为:25
160的桶编号为:21
180的桶编号为:1
200的桶编号为:29
220的桶编号为:9
240的桶编号为:5
260的桶编号为:6
280的桶编号为:2
80的桶编号为:5
67的桶编号为:6
260的桶编号为:6
180桶编号为:5
st 新桶数:32
st 新负载因子:0.6875
st 新最大负载因子:1.0
1 34 260 260 67 67 160 0 180 20 200 40 220 60 240 80 80 100 120 140 280
*/
unordered_map和unordered_multimap都是在_Hash上封装的,和unordered_set一样,提供了28个构造函数和必要的接口
unordered_map类
template<class _Kty,
class _Ty,
class _Hasher = hash<_Kty>,
class _Keyeq = equal_to<_Kty>,
class _Alloc = allocator<pair<const _Kty, _Ty>>>
class unordered_map
: public _Hash<_Umap_traits<_Kty, _Ty,
_Uhash_compare<_Kty, _Hasher, _Keyeq>, _Alloc, false>> // unordered_multimap这里改为 true
{ // hash table of {key, mapped} values, unique keys
}
鉴于map和set的区别,map保存的节点是pair
mapped_type& at(const key_type& _Keyval){
// find element matching _Keyval
iterator _Where = _Mybase::lower_bound(_Keyval);
if (_Where == _Mybase::end())
_Xout_of_range("invalid unordered_map key" );
return (_Where->second);
}
很简单,使用_Hash类定义的查找接口,按照键值查找节点,返回pair的second引用。
mapped_type& operator[](const key_type& _Keyval){
// find element matching _Keyval or insert with default mapped
return (try_emplace(_Keyval).first->second);
}
这里有一个try_emplace接口,意义是:如果该节点存在,则返回;不存在则插入节点 fail if _Keyval present, else emplace
_Pairib _Try_emplace(_Keyty&& _Keyval,
_Mappedty&&... _Mapval){
// fail if _Keyval present, else emplace
iterator _Where = _Mybase::find(_Keyval);
if (_Where == _Mybase::end())
return (_Mybase::emplace(
piecewise_construct,
_STD forward_as_tuple(_STD forward<_Keyty>(_Keyval)),
_STD forward_as_tuple(_STD forward<_Mappedty>(_Mapval)...)));
else
return (_Pairib(_Where, false));
}
_Pairib _Insert_or_assign(_Keyty&& _Keyval,
_Mappedty&& _Mapval){
// assign if _Keyval present, else insert
iterator _Where = _Mybase::find(_Keyval);
if (_Where == _Mybase::end())
return (_Mybase::emplace(
_STD forward<_Keyty>(_Keyval),
_STD forward<_Mappedty>(_Mapval)));
else{
// _Keyval present, assign new value
_Where->second = _STD orward<_Mappedty>(_Mapval);
return (_Pairib(_Where, false));
}
}
注释已经解释的很清楚了,如果节点存在,则修改second值;不存在则插入节点
如有任何问题,还望指出,谢谢~