1、unordered_map和unordered_set的源码分析
源码如下图可见:
由上图可以看出,其实unordered_map和unordered_set的key和key_Value结构也是通过泛型思想进行实现的,通过第一个模版参数value通过传入不同的值,实现key和key_value结构的。
我们观察unordered_set.h实例化hashtable那里的第一个参数传入的是Value,而unordered_map.h实例化hashtable第一个模板参数传入的是pair<const Key, T>,所以我们就知道,unordered_map和unordered_set的实现和前面map和set的实现很相似的,主要的实现都是在hash.h里面实现,unordered_map和unordered_set本质上就是一个壳。
注意一下,就是底层源码的命名风格有点乱,我们这里只是提了个大概,就是他们传入的参数不同从而实现不同的结构这一点。
2、unordered_set和unordered_map的实现
2.1 两个容器大框架的实现
unordered_set和unordered_map的大框架和前面的map和set的实现很相似,两个头文件里面都需要包含不同的一个仿函数对key和key_value进行取值,对于key结构来说就直接返回他的值,而对于key_value来说,存储的是一个pair结构,我们要取到他的key就需要取pair<K, V>.first才可以。
unordered_set.h
#pragma once
#include"hash.h"
namespace lwt
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
//实现一个仿函数进行返回对应的值,key结构就返回key,key_value结构就返回kv.first
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
Hash_Bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht ;
};
}
unordered_map.h
#pragma once
#include"hash.h"
namespace lwt
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
//首先map传入的肯定是一个key_value值
struct KeyOfMap
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private:
Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash> _ht;
};
}
2.2 哈希表结点的实现
我们这里使用前面一篇文章里讲的链地址法,也就是哈希桶进行实现,那么我们知道哈希桶就是一个顺序表的每个位置下挂着一个链表,如下图可见,那么我们就需要一个指向下一个结点的指针,和存储数据的变量,但是这个变量的类型我们不确定,因为我们实现的是泛型结构的,不知道是key结构还是key_value结构,那么我们就可以用一个模板参数T进行代替。
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
2.3 哈希表的迭代器的实现
哈希表的迭代器和前面map和set的迭代器的实现也是类似,只不过他们在一些细节方面,例如++的实现这些有些差异,因为他们的底层结构不同,其他的大差不差,例如实现泛型方面,都是需要加多几个模板参数class Ptr, class Ref, class KeyOfT, class Hash等等,Ptr参数是指定指针的类型,Ref参数是指定引用的类型。
为什么要加这两个,前面我们也提过了,这里再提一遍,就是stl容器里一般都有两种迭代器,一种是iterator的,如果我们分开实现的话代码就过于冗余,所以我们加多几个模板参数进行修改。
然后是KeyOfT和Hash参数,第一个是把key或者key_value结构的值取出来,而Hash则是将关键字的值进行转换成无符号整形进行比较。
// 前置声明
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;
const HT* _ht;
Node* _node;
HTIterator(Node* node, const HT* ht)
:_node(node)
,_ht(ht)
{}
//对迭代器解引用返回数据,就像对一个指针解引用得到指针指向的值
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
{
//kot就是去取出T的key,如果是pair就取first,如果不是就取Key
KeyOfT kot;
//hash就是为了转成size_t进行比较,如果出现字符串啥的就没法直接比较
Hash hash;
size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();
++hashi;
while (hashi < _ht->_table.size())
{
_node = _ht->_table[hashi];
if (_node)
{
break;
}
else
{
hashi++;
}
}
//所有桶都走完了
if (hashi == _ht->_table.size())
{
_node = nullptr;
}
}
return *this;
}
};
至于为什么要前置声明HashTable,那是因为在下面实现迭代器++的时候需要哈希表的数据,而对对象成员变量的查找是往上查找的,我们如果不在上面前置声明一下的话就无法调用到HashTable的数据。
2.4 哈希表的实现
哈希表的基础结构
由于前面哈希表的迭代器需要使用哈希表内的数据,那就需要添加友元,因为哈希表的_table和_n都是私有成员,这里我们给出哈希表的基本结构出来:
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;
HashTable()
:_table(__stl_next_prime(0))
,_n(0)
{}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
_n = 0;
}
private:
vector<Node*> _table;
size_t _n = 0;
};
}
其实大致都和前面哈希表的实现一样,只不过这里是封装了key和key_Value结构,然后添加了迭代器。
Begin和End的实现
begin的实现:首先我们要判断当前的_n是否为0,_n是用来记录里面数据的有多少的,为的是方便后面计算负载因子,如果_n为0的话我们就直接返回end()即可,因为end()的返回肯定是一个nullptr,如果_n不为0,那就需要返回一个存在数据的位置的结点,然后用该结点的地址传给Iterator构造一个匿名对象进行返回即可。
Iterator Begin()
{
if (_n == 0)
{
return End();
}
for (size_t i = 0; i < _table.size();i++)
{
Node* cur = _table[i];
if (cur)
{
return Iterator(cur, this);
}
}
return End();
}
Iterator End()
{
return Iterator(nullptr, this);
}
ConstIterator Begin()const
{
if (_n == 0)
{
return End();
}
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return ConstIterator(cur, this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
Insert和Find的改造
insert和find的实现和前面类似,只不过需要修改返回值,通过查看文档我们发现,Insert返回的是一个pair<iterator, bool>,如果插入成功则会返回当前插入数据的迭代器然后返回pair<cur, true>,如果插入失败说明里面已经有插入的数据了则会返回pair<cur, false>。而在这里面,扩容操作和前面有所不同,前面的是实例化多一个哈希表的类,然后还要开空间,还需要申请新结点,但我们这里可以直接创建一个扩容后的vector,然后把所有的结点放到对应的vecotr的位置处,这样不需要释放那些结点的数据,也不需要重新申请新结点就实现了扩容。如下图可见:
但是也没有那么简单,因为扩容之后容量变了,而我们的哈希函数找数据的位置的公式是hashi = key% M,M为容器的大小,那么容器大小变了,就需要重新计算他们的位置,所以在图里我们看的很简单,但是在代码里还需要计算找出他们的位置在哪里,然后把数据放到newVector之后就让他们两个容器进行交换,那么newVector的容器就走到上面去了,而旧的就走到newVctor处了。
这样就实现了扩容操作。
而Find的实现也是和前面几乎类似,只不过前面哈希表的实现返回的是结点的地址,这里返回迭代器即可。如下代码所示:
pair<Iterator, bool> Insert(const T& data)
{
KeyOfT kot;
Hash hash;
Iterator it = Find(kot(data));
//这里如果it!=end()也就意味着插入的数据已经存在了,所以返回false
if (it != End())
return { it, false };
//负载因子为1的时候扩容
//负载因子就是M/N,M就是当前的空间,N就是容器的空间
if (_n == _table.size())
{
//HashTable<K, V> newht;
newht._tables.resize(__stl_next_prime(_tables.size() + 1));
这里直接*2方便观察
//newht._table.resize(_table.size() * 2);
//for (size_t i = 0; i < _table.size(); i++)
//{
// Node* cur = _table[i];
// while (cur)
// {
// newht.Insert(cur->_kv);
// cur = cur->_next;
// }
//}
//_table.swap(newht._table);
//如果按上面来进行扩容的话需要写一个析构进行释放,然后还要申请新结点
//这里的扩容和前面的不太一样,我们这里可以直接定义一个扩容后容量的vector容器
//然后把那些元素全部连在扩容后的容器内,这样就不需要重新申请空间了。
vector<Node*> newTable(__stl_next_prime(_table.size())+1);
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
//头插到新表里面,但是我们也还要找到他的值才可以
size_t hashi = hash( kot(cur->_data)) % newTable.size();
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hash(kot(data)) % _table.size();
Node* newnode = new Node(data);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return {Iterator(newnode, this), false};
}
Iterator Find(const K& key)
{
KeyOfT kot;
Hash hash;
size_t hashi = hash(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return Iterator(cur, this);
}
cur = cur->_next;
}
return End();
}
Erase的实现:
也是和哈希表的实现几乎类似,只不过我们要加一个KeyOfT,对他们的值进行提取,因为不知道是Key结构还是Key_value结构。
bool Erase(const K& key)
{
KeyOfT kot;
Hash hash;
size_t hashi = hash(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (hash(kot(cur->_data)) == key)
{
if (prev = nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
2.5 套壳操作(完成unordered_map和unordered_set)
unordered_set的套壳实现:
#pragma once
#include"hash.h"
namespace lwt
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
//实现一个仿函数进行返回对应的值,key结构就返回key,key_value结构就返回kv.first
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 beign()
{
return _ht.Begin();
}
const_iterator begin() const
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
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 ;
};
void print(const unordered_set<int>& s)
{
unordered_set<int>::const_iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
}
unordered_map的套壳实现:
#pragma once
#include"hash.h"
namespace lwt
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
//首先map传入的肯定是一个key_value值
struct KeyOfMap
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::Iterator iterator;
typedef typename Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::ConstIterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
const_iterator begin()const
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator end()const
{
return _ht.End();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert({ key, V() });
return ret.first->second;
}
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);
}
private:
Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash> _ht;
};
void test_map1()
{
unordered_map<string, string> dict;
dict.insert({ "sort", "排序" });
dict.insert({ "字符串", "string" });
dict.insert({ "sort", "排序" });
dict.insert({ "left", "左边" });
dict.insert({ "right", "右边" });
dict["left"] = "左边,剩余";
dict["insert"] = "插入";
dict["string"];
for (auto& kv : dict)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
unordered_map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
// 不能修改first,可以修改second
//it->first += 'x';
it->second += 'x';
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
}
END!