哈希桶结构:一个数组,每个元素都是一个单链表结点(开散列)(闭散列只用数组)
map:用仿函数和哈希函数来封装哈希桶。仿函数是为了计算出元素的值(为什么要仿函数计算:后序可以封装set,所以无论是map还是set,存的值都是K-V结构,set存了K-K,也就是两个一样的值),哈希函数用了模板特化,为了能够更方便的计算出string类型的值的哈希位置。
源码:
哈希结点
//哈希结点:实质是一个单链表
template<class V>
struct HashNode
{
V _data;
HashNode<V>* _next;
HashNode(const V& data = V())
:_data(data)
, _next(nullptr)
{ }
};
哈希迭代器
//前置声明
template<class K, class V, class KeyOfValue,class HashFun>
class HashBucket;
//迭代器
//K、V是为了兼容set和map
//KeyOfValue是个仿函数,让set和map封装,然后通过仿函数求出set和map里存的值
//HashFun是哈希函数,是为了区别计算string的值和普通数字的值,从而计算存放位置
template<class K, class V, class KeyOfValue, class HashFun>
struct HashIterator
{
typedef HashNode<V> Node;
typedef Node* pNode;
typedef HashIterator<K, V, KeyOfValue, HashFun> Self;
typedef typename HashBucket<K, V, KeyOfValue, HashFun> HSbucket;
pNode _node;
HSbucket* _hb;
HashIterator(pNode node, HSbucket* hb)
:_node(node)
, _hb(hb)
{
}
V& operator*()
{
return _node->_data;
}
V* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
Self& operator++()
{
//如果链表结点的下一个还有值,则下一个结点就是++后的值
if (_node->_next)
{
_node = _node->_next;
}
else//否则就由数组往后找,直到找到数组有值的位置
{
KeyOfValue kov;
HashFun hf;
int table_size = _hb->_table.size();
//先通过哈希函数和KeyOfValue计算出当前位置
int index = hf(kov(_node->_data)) % table_size;
//然后遍历数组去查找数组的值
++index;
for (; index < table_size; ++index)
{
//如果不是空就是找到了
if (_hb->_table[index] != nullptr)
{
_node = _hb->_table[index];
break;
}
}
//如果走到最后还没有找到,就是没有了,就是end()
if (index == table_size)
{
_node = nullptr;
}
}
return *this;
}
};
哈希桶
template<class K, class V, class KeyOfValue, class HashFun>
class HashBucket
{
public:
//声明为友元是为了让自己定义的迭代器访问到自己的private里的table
friend HashIterator<K, V, KeyOfValue,HashFun>;
typedef HashNode<V> Node;
typedef Node* pNode;
typedef typename HashIterator<K, V, KeyOfValue,HashFun> iterator;
private:
vector<pNode> _table;
size_t _size;
public:
iterator begin()
{
//begin就是数组里第一个有值的元素
for (size_t i = 0; i < _table.size(); ++i)
{
if (_table[i])
{
return iterator(_table[i], this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
//end就是值为空的迭代器
return iterator(nullptr, this);
}
HashBucket()
:_size(0)
{
_table.resize(5, nullptr);
}
pair<iterator,bool> Insert(const V& data)
{
//检查容量
ChekckCapacity();
//先确定数组下标
KeyOfValue kov;
HashFun hf;
int index = hf(kov(data)) % _table.size();
//插入
pNode cur = _table[index];
//判断是否已经有这个元素,直到cur为空
while (cur)
{
if (hf(kov(cur->_data)) == hf(kov(data)))
{
return make_pair(iterator(cur, this), false);
}
cur = cur->_next;
}
//创建新节点
cur = new Node(data);
//让新节点指向这个链表的头,头插
cur->_next = _table[index];
//更新数组的元素
_table[index] = cur;
++_size;
return make_pair(iterator(cur, this), true);
}
void ChekckCapacity()
{
//负载因子可以是1
if (_size / _table.size() < 1)
{
return;
}
KeyOfValue kov;
HashFun hf;
vector<pNode> new_table(_size * 2);
for (int i = 0; i < _table.size(); ++i)
{
pNode cur = _table[i];
while (cur)
{
//先保存后一个节点
pNode next = cur->_next;
//确定这个数据在新表的位置
int index = hf(kov(cur->_data)) % new_table.size();
//头插
cur->_next = new_table[index];
//更新数组元素
new_table[index] = cur;
//继续遍历
cur = next;
}
_table[i] = nullptr;
}
_table.swap(new_table);
}
};
哈希函数
//为了得到普通数字的数值
template<class K>
struct HashFunction
{
int operator()(const K& key)
{
return key;
}
};
//这里是模板特化,编译器会先去找有特化的仿函数,这里是为了计算string的值,得到string需要存放的位置
template<>
struct HashFunction<string>
{
int operator()(const string& key)
{
int hash = 0;
for (const auto& ch : key)
{
//这里的值的计算方式不唯一,有很多种方法
hash = hash * 31 + ch;
}
return hash;
}
};
map
//模拟unordered_map
template<class K, class V, class HashFun = HashFunction<K>>
class MyUnorderedMap
{
//这个是为了计算出map键值对里的K的仿函数
struct MapKeyOfValue
{
//如果是set则只要传入一个K,返回一个K即可
const K& operator()(const pair<K, V>& data)
{
return data.first;
}
};
private:
//底层实际就是通过哈希函数和仿函数来封装了哈希桶
HashBucket<K, pair<K, V>, MapKeyOfValue, HashFun> _hb;
public:
typedef typename HashBucket<K, pair<K, V>, MapKeyOfValue, HashFun>::iterator iterator;
pair<iterator, bool> Insert(const pair<K, V>& data)
{
return _hb.Insert(data);
}
iterator begin()
{
return _hb.begin();
}
iterator end()
{
return _hb.end();
}
V& operator[](const K& key)
{
//这个函数的原理是:
//如果访问的值已经存在,返回已存在的元素的引用
//如果不存在,一个值:key-默认的V()的值,返回插入元素的引用
//ret的second是插入成功或者失败,first是访问的那个值所在的键值对
pair<iterator, bool> ret = _hb.Insert(make_pair(key, V()));
//所以first->second就是键值对里的value的引用,返回后用户可以修改这个value
return ret.first->second;
}
};