【深度探索 STL】hashtable(哈希表)

哈希表是一个很重要的数据结构,在各大场合均有应用,比如在内核网络栈中的 sock_array 的结构。

之前也学习过哈希表 ,建议看此文之前,先看前面用C++实现的简洁的哈希表。这里通过剖析SGI STL 中的hashtable 来进一步探索hashtable。

SGI STL 中哈希表采用链接法解决冲突。结构中维护了一个 vector,vector 中每一个元素称为一个桶(bucket),它包含的是一个链表的第一个节点。

哈希表的思想这里就不赘述了,直接通过STL 的源码来剖心其内部实现。

一、 hash table 的节点定义:

template <class Value>
struct __hashtable_node
{
  __hashtable_node* next; //下一个节点
  Value val; //键值
}; 
bucket 所维护的正是上述的 hash table node。

                            

从上图可知,buckets vector 中存放的是bucket,然后每个bucket 存放链表(如果有的话)。通俗的说bucket 就是动态array 中的一个元素。

二、hashtable 的迭代器:

template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
	//typedef 定义
	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;

	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* ht;  //保持对容器的连结关系(因为可能需要从bucket跳到bucket)

	/*构造函数*/
	__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
	__hashtable_iterator() {}

	/*运算符重载*/
	reference operator*() const { return cur->val; }
 
	pointer operator->() const { return &(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; }
};
hashtable 迭代器必须永远维系着与整个“bucket vector” 的关系,并记录目前所指的节点。其前进操作是首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于 list 内,所以利用节点的 next 指针即可轻易达成前进操作。如果目前节点正巧是list 的尾端,就跳至下一个bucket 身上,那正是指向下一个list 的头部节点。上面的 hashtable* ht; 就是为这点添加的。

/*前缀++*/
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;//更新为当前bucket链表中的下一个节点

	if (!cur) {//如果该节点恰好为list 的尾端
		size_type bucket = ht->bkt_num(old->val);//通过散列函数查找键值对应的bucket
		while (!cur && ++bucket < ht->buckets.size())//根据元素值定位下一个bucket的起头处
			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;//调用operator++
	return tmp;//返回自增前的值
}
三、hashtable 的数据结构

由于在STL源码中 hashtable 的操作函数都定义在结构体类,这里我们只摘取一部分,具体函数实现在后面介绍

/*hashtable节点结构,处于篇幅考虑,这里省去对应const类型code*/
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey,
class Alloc>
class hashtable {
public:
	typedef Key key_type;//节点的键值型别
	typedef Value value_type;//节点的实值型别
	typedef HashFcn hasher;//散列函数的函数型别
	typedef EqualKey key_equal;//判断键值相同与否的方法

	/*
	ExtractKey:从节点中取出键值的方法
	Alloc:空间配置器,缺省使用std::alloc
	*/

	typedef size_t            size_type;
	typedef ptrdiff_t         difference_type;
	typedef value_type*       pointer;
	typedef const value_type* const_pointer;
	typedef value_type&       reference;
	typedef const value_type& const_reference;

	hasher hash_funct() const { return hash; }
	key_equal key_eq() const { return equals; }

private:
	hasher hash;
	key_equal equals;
	ExtractKey get_key;

	typedef __hashtable_node<Value> node;
	typedef simple_alloc<node, Alloc> node_allocator;

	vector<node*, Alloc> buckets;//桶(动态array)
	size_type num_elements;//散列表中所有元素的个数

public:
	/*typedef 定义*/
	typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey,
		Alloc>
		iterator;

	typedef __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey,
		Alloc>
		const_iterator;

	friend struct
		__hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;
	friend struct
		__hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;

public:
	/*构造函数*/
	hashtable(size_type n,
		const HashFcn&    hf,
		const EqualKey&   eql,
		const ExtractKey& ext)
		: hash(hf), equals(eql), get_key(ext), num_elements(0)
	{
		initialize_buckets(n);//初始化
	}

	hashtable(size_type n,
		const HashFcn&    hf,
		const EqualKey&   eql)
		: hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
	{
		initialize_buckets(n);
	}

	/*拷贝构造函数*/
	hashtable(const hashtable& ht)
		: hash(ht.hash), equals(ht.equals), get_key(ht.get_key), num_elements(0)
	{
		copy_from(ht);
	}
	/*赋值构造函数*/
	hashtable& operator= (const hashtable& ht)
	{
		if (&ht != this) {
			clear();//清除原有数据
			hash = ht.hash;
			equals = ht.equals;
			get_key = ht.get_key;
			copy_from(ht);
		}
		return *this;
	}
	/*析构函数*/
	~hashtable() { clear(); }

	size_type size() const { return num_elements; }//实际元素
	size_type max_size() const { return size_type(-1); }
	bool empty() const { return size() == 0; }//判断是否为空

	/*类里面,this* 与ht交换,浅交换*/
	void swap(hashtable& ht)
	{
		::swap(hash, ht.hash);
		::swap(equals, ht.equals);
		::swap(get_key, ht.get_key);
		buckets.swap(ht.buckets);
		::swap(num_elements, ht.num_elements);
	}

	/*迭代器有效首位置*/
	iterator begin()
	{
		for (size_type n = 0; n < buckets.size(); ++n)
		if (buckets[n])
			/*下面实质就是__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab){}*/
			return iterator(buckets[n], this);
		return end();
	}

	/*迭代器尾端位置的下一位置,其实是空位置*/
	iterator end() { return iterator(0, this); }

	/*==运算符重载*/
	friend bool operator== (const hashtable<Value, Key,
		HashFcn, ExtractKey, EqualKey,
		Alloc>&,
		const hashtable<Value, Key,
		HashFcn, ExtractKey, EqualKey,
		Alloc>&);

public:
	/*返回桶子的个数(vector的大小)*/
	size_type bucket_count() const { return buckets.size(); }
	/*可能的最大值(质数表里的最大值)*/
	size_type max_bucket_count() const
	{
		return __stl_prime_list[__stl_num_primes - 1];
	}

	/*指定第bucket个桶中节点的个数*/
	size_type elems_in_bucket(size_type bucket) const
	{
		size_type result = 0;
		for (node* cur = buckets[bucket]; cur; cur = cur->next)//某bucket中链表节点的个数
			result += 1;
		return result;
	}
	/*插入单个元素,不允许重复*/
	pair<iterator, bool> insert_unique(const value_type& obj)
	{
		resize(num_elements + 1);//判断是否需要重建表格,如需要就扩充
		return insert_unique_noresize(obj);
	}
	/*插入单个元素,允许重复*/
	iterator insert_equal(const value_type& obj)
	{
		resize(num_elements + 1);
		return insert_equal_noresize(obj);
	}

	pair<iterator, bool> insert_unique_noresize(const value_type& obj);
	iterator insert_equal_noresize(const value_type& obj);

	/*下面都是批量元素插入*/
	……

	/*两个连续存储区之间的元素插入*/
	void insert_unique(const value_type* f, const value_type* l)
	{
		size_type n = l - f;
		resize(num_elements + n);
		for (; n > 0; --n, ++f)
			insert_unique_noresize(*f);
	}

	void insert_equal(const value_type* f, const value_type* l)
	{
		size_type n = l - f;
		resize(num_elements + n);
		for (; n > 0; --n, ++f)
			insert_equal_noresize(*f);
	}

	/*插入两个迭代器之间的元素,不允许重复*/
	void insert_unique(const_iterator f, const_iterator l)
	{
		size_type n = 0;
		distance(f, l, n);//迭代器距离
		resize(num_elements + n);
		for (; n > 0; --n, ++f)
			insert_unique_noresize(*f);//两个迭代器之间的元素逐步插入
	}

	/*插入两个迭代器之间的元素,允许重复*/
	void insert_equal(const_iterator f, const_iterator l)
	{
		size_type n = 0;
		distance(f, l, n);//迭代器距离
		resize(num_elements + n);
		for (; n > 0; --n, ++f)
			insert_equal_noresize(*f);//两个迭代器之间的元素逐步插入(这里的两个迭代器之间不是线性的)
	}

	reference find_or_insert(const value_type& obj);

	/*查找*/
	iterator find(const key_type& key)
	{
		size_type n = bkt_num_key(key);//定位
		node* first;
		/*直接在下面循环中完成搜索*/
		for (first = buckets[n];
			first && !equals(get_key(first->val), key);
			first = first->next)
		{
		}
		return iterator(first, this);
	}

	/*统计与键值key相等的元素的个数*/
	size_type count(const key_type& key) const
	{
		const size_type n = bkt_num_key(key);//定位
		size_type result = 0;

		for (const node* cur = buckets[n]; cur; cur = cur->next)
		if (equals(get_key(cur->val), key))
			++result;
		return result;
	}

	pair<iterator, iterator> equal_range(const key_type& key);
	pair<const_iterator, const_iterator> equal_range(const key_type& key) const;

	size_type erase(const key_type& key);
	void erase(const iterator& it);
	void erase(iterator first, iterator last);

	void erase(const const_iterator& it);
	void erase(const_iterator first, const_iterator last);

	void resize(size_type num_elements_hint);
	void clear();

private:
	size_type next_size(size_type n) const { return __stl_next_prime(n); }

	/*buckes初始化*/
	void initialize_buckets(size_type n)
	{
		const size_type n_buckets = next_size(n);//质数表里大于等于该数的最小质数
		buckets.reserve(n_buckets);//调整buckets大小(buckets其实就是一个vector)
		buckets.insert(buckets.end(), n_buckets, (node*)0);
		num_elements = 0;
	}

	/*下面近似是散列函数,判知元素的落脚处*/
	/*只接受键值*/
	size_type bkt_num_key(const key_type& key) const
	{
		return bkt_num_key(key, buckets.size());
	}
	/*只接受实值(value)*/
	size_type bkt_num(const value_type& obj) const
	{
		return bkt_num_key(get_key(obj));
	}
	/*接受键值和 buckets个数*/
	size_type bkt_num_key(const key_type& key, size_t n) const
	{
		return hash(key) % n;
	}
	/*接受实值(value)和 buckets个数*/
	size_type bkt_num(const value_type& obj, size_t n) const
	{
		return bkt_num_key(get_key(obj), n);
	}

	/*创建新节点*/
	node* new_node(const value_type& obj)
	{
		node* n = node_allocator::allocate();
		n->next = 0;

		try {
			construct(&n->val, obj);
			return n;
		}
		catch (...) {
			node_allocator::deallocate(n);
			throw;
		}
	}
	/*销毁*/
	void delete_node(node* n)
	{
		destroy(&n->val);
		node_allocator::deallocate(n);
	}

	void erase_bucket(const size_type n, node* first, node* last);
	void erase_bucket(const size_type n, node* last);
	void copy_from(const hashtable& ht);
};

补充:

质数表:SGI STL 中的hashtable 是采用质数来设定表格大小

/*质数表*/
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
	53, 97, 193, 389, 769,
	1543, 3079, 6151, 12289, 24593,
	49157, 98317, 196613, 393241, 786433,
	1572869, 3145739, 6291469, 12582917, 25165843,
	50331653, 100663319, 201326611, 402653189, 805306457,
	1610612741, 3221225473, 4294967291
};

/*以下找出上述28个质数之中,最接近并大于 n的那个质数(有的话),没有取最大*/
inline unsigned long __stl_next_prime(unsigned long n)
{
	const unsigned long* first = __stl_prime_list;//首
	const unsigned long* last = __stl_prime_list + __stl_num_primes;//尾的下一位置
	/*泛型算法,返回一个迭代器,指向第一个不小于 n的元素*/
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;//如果没有比它大的就取最大的
}

这里考虑到元素的各种型别,将散列函数封装起来

template <class Key> struct hash { };
/*字符串类型*/
inline size_t __stl_hash_string(const char* s)
{
	/*处理方法*/
	unsigned long h = 0;
	for (; *s; ++s)
		h = 5 * h + *s;

	return size_t(h);
}

/*字符串类型,则调用上面处理方法*/
struct hash<char*>
{
	size_t operator()(const char* s) const { return __stl_hash_string(s); }
};

struct hash<const char*>
{
	size_t operator()(const char* s) const { return __stl_hash_string(s); }
};

/*下面各种类型直接返回,无需额外处理*/
struct hash<char> {
	size_t operator()(char x) const { return x; }
};
struct hash<unsigned char> {
	size_t operator()(unsigned char x) const { return x; }
};
struct hash<signed char> {
	size_t operator()(unsigned char x) const { return x; }
};
struct hash<short> {
	size_t operator()(short x) const { return x; }
};
struct hash<unsigned short> {
	size_t operator()(unsigned short x) const { return x; }
};
struct hash<int> {
	size_t operator()(int x) const { return x; }
};
struct hash<unsigned int> {
	size_t operator()(unsigned int x) const { return x; }
};
struct hash<long> {
	size_t operator()(long x) const { return x; }
};
struct hash<unsigned long> {
	size_t operator()(unsigned long x) const { return x; }
};

插入操作与表格重整

/*以下函数判断是否需要重建表格。如果不需要,立即返回。如果需要,就重建*/
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();//bucket vector 的大小
	/*如果元素个数(把新增元素计入后)比bucket vector 大,则需要重建表格*/
	if (num_elements_hint > old_n) {
		const size_type n = next_size(num_elements_hint);//找出下一个质数
		
		if (n > old_n) { //old_n不是质数表里面的最大值时,才可扩展
			vector<node*, A> tmp(n, (node*)0);//设立新的bucket vector,大小为n
			try {
				//以下处理每一个旧的bucket
				for (size_type bucket = 0; bucket < old_n; ++bucket) {
					node* first = buckets[bucket];//指向节点所对应之串行(链表)的起始节点
					while (first) {//处理单个bucket中的链表
						size_type new_bucket = bkt_num(first->val, n);//找出节点落在哪一个新的bucket内
						buckets[bucket] = first->next;//令旧bucket指向其所对应的链表的下一个节点,以便迭代处理

						/*下面将当前节点插入到新的bucket内,成为其对应链表的第一个节点,这里的实现比较巧妙
						相当于插入新节点到新bucket vector中,新插入的元素插入到链表的首位置,这里不同于一般的插入的是,
						由于之前已有元素占据空间,这里只是修改节点指针指向*/
						first->next = tmp[new_bucket];
						tmp[new_bucket] = first;

						first = buckets[bucket];//回到旧bucket所指的待处理链表,准备处理下一个节点
						                        //first = old_first->next;
					}
				}
				buckets.swap(tmp);//vector::swap 新旧两个buckets 对调(浅修改)
				/*对调两方如果大小不同,大的会变小,小的会变大,离开时释放local tmp 的内存*/
			}
			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;
			}
		}
	}
}

/*插入元素,不允许重复*/
template <class V, class K, class HF, class Ex, class Eq, class A>
pair<hashtable<V, K, HF, Ex, Eq, A>::iterator, bool>
hashtable<V, K, HF, Ex, Eq, A>::insert_unique_noresize(const value_type& obj)
{
	const size_type n = bkt_num(obj);//定位bucket
	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);//产生新节点 node_allocator::allocate()
	/*先插入节点放在链表最前面*/
	tmp->next = first;
	buckets[n] = tmp;
	++num_elements;//元素个数增加
	return pair<iterator, bool>(iterator(tmp, this), true);
}

/*插入元素,允许重复*/
template <class V, class K, class HF, class Ex, class Eq, class A>
hashtable<V, K, HF, Ex, Eq, A>::iterator
hashtable<V, K, HF, Ex, Eq, A>::insert_equal_noresize(const value_type& obj)
{
	const size_type n = bkt_num(obj);//定位bucket
	node* first = buckets[n];//链表头节点

	for (node* cur = first; cur; cur = cur->next)
	if (equals(get_key(cur->val), get_key(obj))) {//如果插入元素是重复的(与cur->val重复)
		node* tmp = new_node(obj);
		tmp->next = cur->next;//新增元素插入重复元素的后面
		cur->next = tmp;
		++num_elements;
		return iterator(tmp, this);
	}
	//没有重复,等同于insert_unique_noresize()
	node* tmp = new_node(obj);
	tmp->next = first;
	buckets[n] = tmp;
	++num_elements;
	return iterator(tmp, this);
}                                                                                                                                                         

                            

                  

插入过程,可以用上面两张图(来源wikipedia)来说明。

复制与整体删除

/*复制*/
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::copy_from(const hashtable& ht)
{
	buckets.clear();//清除所有原有元素
	buckets.reserve(ht.buckets.size());//bucket vector 空间调整为 ht对应的大小
	buckets.insert(buckets.end(), ht.buckets.size(), (node*)0);//从尾端开始插入n个元素,其值为NULL指针
	//此时buckets vector 为空,所以所谓尾端,就是起头处
	try {
		for (size_type i = 0; i < ht.buckets.size(); ++i) {
			//这里是复制vector 中的每一个元素
			if (const node* cur = ht.buckets[i]) {//如果该位置不为空,表明有元素需要复制
				node* copy = new_node(cur->val);
				buckets[i] = copy;//先是bucket vector 中的元素

				//然后是针对同一个bucket list,复制每一个节点
				for (node* next = cur->next; next; cur = next, next = cur->next) {
					copy->next = new_node(next->val);
					copy = copy->next;
				}
			}
		}
		num_elements = ht.num_elements;
	}
	catch (...) {
		clear();
		throw;
	}
}

/*删除第 n 个bucket中的所有元素(链表也要逐个删除)*/
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)
{
	node* cur = buckets[n];
	while (cur != last) {
		node* next = cur->next;
		delete_node(cur);
		cur = next;
		buckets[n] = cur;
		--num_elements;
	}
}

/*整体删除,考虑链表的存在,需要逐个删除释放内存*/
template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::clear()
{
	//针对每一个bucket
	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;
	//此时bucket vector 空间并未释放
}

水平有限,如有错误,欢迎指正。
参考资料:《STL 源码剖析》

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值