SGISTL源码阅读二十三 hashtable下
前言
之前介绍了hashtable
的基本结构和它的构造方法,现在我们继续来学习它的迭代器和一些常用方法。
深入源码
hashtable
的迭代器
//迭代器的声明
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator;
//和__hashtable_iterator类似,此处只列出__hashtable_iterator的实现
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_const_iterator;
/* Value:值的类型
* Key:键的类型
* HashFcn:哈希函数的函数型别(对象)
* ExtractKey:从节点取出键的方法(对象)
* EqualKey:判断键是否相同的方法(对象)
* Alloc:空间配置器
* 关于函数对象,它其实就是重载了()操作符,之后便可以像函数指针一样使用了,并且可以传递附加数据
*/
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>
hashtable;
typedef __hashtable_iterator<Value, Key, HashFcn,
ExtractKey, EqualKey, Alloc>
iterator;
typedef __hashtable_const_iterator<Value, Key, HashFcn,
ExtractKey, EqualKey, Alloc>
const_iterator;
typedef __hashtable_node<Value> node;
//声明相应型别
//我们可以看到迭代器类型是forward_iterator_tag
//因为在buckets中节点是以链表的形式存储的
typedef forward_iterator_tag iterator_category;
typedef Value value_type;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
typedef Value& reference;
typedef Value* pointer;
//指向当前节点
node* cur;
/* 迭代器指向的节点所属的hashtable节点
* 因为涉及到跳bucket的情况
* 需要知道迭代器指向是哪个容器,所以需要hashtable* ht这个变量
*/
hashtable* ht;
//构造函数
__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
__hashtable_iterator() {}
//操作符重载
//返回当前节点的value值
reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
//重载->
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
//操作符重载的声明
iterator& operator++();
iterator operator++(int);
bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur != it.cur; }
};
//...
//重载前++
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
//暂存当前按节点
const node* old = cur;
cur = cur->next;
//如果当前节点的下一个节点为空
if (!cur) {
//获取当前节点的bucket号
size_type bucket = ht->bkt_num(old->val);
//跳到下一个bucket去
while (!cur && ++bucket < ht->buckets.size())
cur = ht->buckets[bucket];
}
return *this;
}
//重载后++
template <class V, class K, class HF, class ExK, class EqK, class A>
inline __hashtable_iterator<V, K, HF, ExK, EqK, A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++(int)
{
iterator tmp = *this;
//调用前++
++*this;
return tmp;
}
resize
顾名思义,该函数的作用是实现当前hashtable
扩容,我们知道hashtable
中的buckets
底层是由vector
实现的,当容量不足是,vector
的扩容机制是直接扩充到它原来大小的两倍,但是在hashtable
中,有28个质数,桶的大小只能从里面选取,所以我们不能使用vector
内部的扩容机制,只能自己实现。
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::resize(size_type num_elements_hint)
{
//获取当前桶的数量
const size_type old_n = buckets.size();
//如果要求改变的值大于原来桶的数量(扩容)
//如果不满足则什么也不用做
if (num_elements_hint > old_n) {
//获取一个合适的质数
const size_type n = next_size(num_elements_hint);
//如果还是比原来的桶数大
if (n > old_n) {
//创建一个新的vector
vector<node*, A> tmp(n, (node*) 0);
__STL_TRY {
//将当前buckets中的节点复制到tmp中去
for (size_type bucket = 0; bucket < old_n; ++bucket) {
node* first = buckets[bucket];
while (first) {
size_type new_bucket = bkt_num(first->val, n);
buckets[bucket] = first->next;
first->next = tmp[new_bucket];
tmp[new_bucket] = first;
first = buckets[bucket];
}
}
//交换两个buckets
//完成了扩容操作
buckets.swap(tmp);
}
# ifdef __STL_USE_EXCEPTIONS
//处理异常情况,将新申请的vector中的空间全部释放,并抛出异常
catch(...) {
for (size_type bucket = 0; bucket < tmp.size(); ++bucket) {
while (tmp[bucket]) {
node* next = tmp[bucket]->next;
delete_node(tmp[bucket]);
tmp[bucket] = next;
}
}
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
}
}
hashtable
的插入操作
insert_unique_noresize
保证唯一性
template <class V, class K, class HF, class Ex, class Eq, class A>
pair<typename hashtable<V, K, HF, Ex, Eq, A>::iterator, bool>
hashtable<V, K, HF, Ex, Eq, A>::insert_unique_noresize(const value_type& obj)
{
//获取当前节点应该放入的的bucket号
const size_type n = bkt_num(obj);
node* first = buckets[n];
//遍历该桶,判断是否有重复节点
for (node* cur = first; cur; cur = cur->next)
//如果有重复的节点存在,则直接返回当前节点的位置和插入失败的信息
if (equals(get_key(cur->val), get_key(obj)))
return pair<iterator, bool>(iterator(cur, this), false);
//新建一个节点
node* tmp = new_node(obj);
//以头插法装入桶中
tmp->next = first;
buckets[n] = tmp;
++num_elements;
//返回当前节点的位置和插入成功的信息
return pair<iterator, bool>(iterator(tmp, this), true);
}
insert_unique
保证元素唯一唯一性的插入方式,根据不同的情况有很多的重载版本,因为过多,且思想基本一致,所以只列出了一部分。
pair<iterator, bool> insert_unique(const value_type& obj)
{
//判断是否需要扩容
resize(num_elements + 1);
return insert_unique_noresize(obj);
}
template <class InputIterator>
void insert_unique(InputIterator f, InputIterator l)
{
insert_unique(f, l, iterator_category(f));
}
template <class InputIterator>
void insert_unique(InputIterator f, InputIterator l,
input_iterator_tag)
{
for ( ; f != l; ++f)
insert_unique(*f);
}
template <class ForwardIterator>
void insert_unique(ForwardIterator f, ForwardIterator l,
forward_iterator_tag)
{
size_type n = 0;
distance(f, l, n);
resize(num_elements + n);
for ( ; n > 0; --n, ++f)
insert_unique_noresize(*f);
}
//...
insert_equal_noresize
template <class V, class K, class HF, class Ex, class Eq, class A>
typename hashtable<V, K, HF, Ex, Eq, A>::iterator
hashtable<V, K, HF, Ex, Eq, A>::insert_equal_noresize(const value_type& obj)
{
//获取当前节点应该放入的的bucket号
const size_type n = bkt_num(obj);
node* first = buckets[n];
for (node* cur = first; cur; cur = cur->next)
if (equals(get_key(cur->val), get_key(obj))) {
//如果有重复节点则之间插在它的前面
node* tmp = new_node(obj);
tmp->next = cur->next;
cur->next = tmp;
++num_elements;
return iterator(tmp, this);
}
//如果没用重复节点
//就插在桶的最前面
node* tmp = new_node(obj);
tmp->next = first;
buckets[n] = tmp;
++num_elements;
return iterator(tmp, this);
}
insert_equal
不保证元素唯一唯一性的插入方式,根据不同的情况有很多的重载版本,因为过多,且思想基本一致,所以只列出了一部分。
iterator insert_equal(const value_type& obj)
{
//判断是否需要扩容
resize(num_elements + 1);
return insert_equal_noresize(obj);
}
template <class InputIterator>
void insert_equal(InputIterator f, InputIterator l)
{
insert_equal(f, l, iterator_category(f));
}
template <class InputIterator>
void insert_equal(InputIterator f, InputIterator l,
input_iterator_tag)
{
for ( ; f != l; ++f)
insert_equal(*f);
}
template <class ForwardIterator>
void insert_equal(ForwardIterator f, ForwardIterator l,
forward_iterator_tag)
{
size_type n = 0;
distance(f, l, n);
resize(num_elements + n);
for ( ; n > 0; --n, ++f)
insert_equal_noresize(*f);
}
//...
find_or_insert
查找某个值,如果没有找到则把该值插入进去
template <class V, class K, class HF, class Ex, class Eq, class A>
typename hashtable<V, K, HF, Ex, Eq, A>::reference
hashtable<V, K, HF, Ex, Eq, A>::find_or_insert(const value_type& obj)
{
//判断是否需要扩容
//这里不是很懂,因为不是所有的情况都需要插入元素进去
//如果调用了这个函数那么就可能会扩容,但是要是扩容了又没有插入元素进去,岂不是很划不来
resize(num_elements + 1);
size_type n = bkt_num(obj);
node* first = buckets[n];
//遍历整个桶
for (node* cur = first; cur; cur = cur->next)
//如果找到就返回val
if (equals(get_key(cur->val), get_key(obj)))
return cur->val;
//没有找到就新建一个节点将它插入进去
node* tmp = new_node(obj);
tmp->next = first;
buckets[n] = tmp;
++num_elements;
return tmp->val;
}
hashtable
的删除操作
erase_bucket
//指定node范围删除桶
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::erase_bucket(const size_type n,
node* first, node* last)
{
//指向当前bucket
node* cur = buckets[n];
//从桶的头部删除到last指向的位置
if (cur == first)
erase_bucket(n, last);
else {
node* next;
//找到开始删除的位置
for (next = cur->next; next != first; cur = next, next = cur->next)
;
//依次删除节点
while (next) {
cur->next = next->next;
delete_node(next);
next = cur->next;
--num_elements;
}
}
}
//从桶的头部删除到last指向的位置
template <class V, class K, class HF, class Ex, class Eq, class A>
void
hashtable<V, K, HF, Ex, Eq, A>::erase_bucket(const size_type n, node* last)
{
//指向当前bucket
node* cur = buckets[n];
//释放所有节点
while (cur != last) {
node* next = cur->next;
delete_node(cur);
cur = next;
buckets[n] = cur;
--num_elements;
}
}
erase
它的删除操作也有非常多重载版本,但是核心思想都是类似的,我只选取了部分拿来讲解
template <class V, class K, class HF, class Ex, class Eq, class A>
typename hashtable<V, K, HF, Ex, Eq, A>::size_type
hashtable<V, K, HF, Ex, Eq, A>::erase(const key_type& key)
{
//获取当前key值所在的bucket号
const size_type n = bkt_num_key(key);
node* first = buckets[n];
size_type erased = 0;
//如果bucket不为空
if (first) {
node* cur = first;
node* next = cur->next;
//遍历找到key值的节点,删除它
while (next) {
if (equals(get_key(next->val), key)) {
cur->next = next->next;
delete_node(next);
next = cur->next;
++erased;
--num_elements;
}
else {
cur = next;
next = cur->next;
}
}
//将头节点分开处理,因为是链表
if (equals(get_key(first->val), key)) {
buckets[n] = first->next;
delete_node(first);
++erased;
--num_elements;
}
}
//返回删除元素的数目
return erased;
}
//...
//指定范围删除节点
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::erase(iterator first, iterator last)
{
//如果迭代器当前节点不为空,则返回当前桶号,否则返回当前桶的数量
size_type f_bucket = first.cur ? bkt_num(first.cur->val) : buckets.size();
size_type l_bucket = last.cur ? bkt_num(last.cur->val) : buckets.size();
//如果指向了同一个位置,就什么也不做
if (first.cur == last.cur)
return;
//说明两个迭代器指向的是同一个桶
//调用 erase_bucket
else if (f_bucket == l_bucket)
erase_bucket(f_bucket, first.cur, last.cur);
//两个迭代器指向的不是同一个桶
else {
erase_bucket(f_bucket, first.cur, 0);
for (size_type n = f_bucket + 1; n < l_bucket; ++n)
erase_bucket(n, 0);
if (l_bucket != buckets.size())
erase_bucket(l_bucket, last.cur);
}
}
clear
//清空所有桶中的节点
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::clear()
{
//遍历所有的桶
for (size_type i = 0; i < buckets.size(); ++i) {
node* cur = buckets[i];
//释放掉桶中的节点
while (cur != 0) {
node* next = cur->next;
delete_node(cur);
cur = next;
}
buckets[i] = 0;
}
num_elements = 0;
}
操作符重载
//重载=
hashtable& operator= (const hashtable& ht)
{
if (&ht != this) {
//先将当前hashtable清空
clear();
//再依次复制
hash = ht.hash;
equals = ht.equals;
get_key = ht.get_key;
//调用copy_from
copy_from(ht);
}
return *this;
}
//重载==
template <class V, class K, class HF, class Ex, class Eq, class A>
bool operator==(const hashtable<V, K, HF, Ex, Eq, A>& ht1,
const hashtable<V, K, HF, Ex, Eq, A>& ht2)
{
typedef typename hashtable<V, K, HF, Ex, Eq, A>::node node;
//如果两个hashtable的长度不等,直接返回false
if (ht1.buckets.size() != ht2.buckets.size())
return false;
//依次比较两个hashtable桶中的节点
for (int n = 0; n < ht1.buckets.size(); ++n) {
node* cur1 = ht1.buckets[n];
node* cur2 = ht2.buckets[n];
for ( ; cur1 && cur2 && cur1->val == cur2->val;
cur1 = cur1->next, cur2 = cur2->next)
{}
//如果存在不相等的情况直接返回false
if (cur1 || cur2)
return false;
}
return true;
}
总结
我们分析了hashtable
的迭代器以及插入删除等操作。
针对不同的情况,同一种操作有很多中重载的版本,可以看出STL是非常注重效率问题的。
接下来我们将要介绍到hashset
和hashmap
,他们的底层容器就是hashtable
。