STL源码剖析 hashtable

  • 二叉搜索树具有对数平均时间的表现,但是这个需要满足的假设前提是输入的数据需要具备随机性
  • hashtable 散列表这种结构在插入、删除、搜寻等操作层面上也具有常数平均时间的表现。而且不需要依赖元素的随机性,这种表现是以统计为基础的

hashtable的概述

  • hashtable可提供对任何有名项的存取和删除操作
  • 因为操作的对象是有名项,因此hashtable可以作为一种字典结构
  • 将一个元素映射成为一个 “大小可以接受的索引”简称为hash function散列函数
  • 考虑到元素的个数大于array的容量,可能有不同的元素被映射到相同的位置,简称为碰撞
  • 解决碰撞的方法有很多,线性探测、二次探测、开链

线性探测

  • 负载系数:元素的个数 除以表格的大小 ,负载系数介于0-1,除非使用开链法
  • 使用线性探测时,根据散列函数计算得到的位置已经存在了元素,就需要循环往下一一寻找,如果到达array的尾端,就需要绕回到头部继续寻找,直到找到一个可用的空间为止。
  • 元素的搜寻也是类似
  • 元素的删除采用惰性机制,只标记删除的记号,实际真正的删除操作需要等待表格重新整理时才可以进行

  • 需要两个假设:1,表格足够大;2,每个元素都够独立 (如果所有元素通过散列函数计算都得到相同的位置,造成了平均插入成本的厂长速度远远高于了负载系数的成长速度)

二次探测

  • F(i) = i^2,如果计算得到新元素的位置是H,但是这个位置已经被占用了,将会依序尝试 H+1^2 H+2^2  H+3^2 等等,而不是H+1 H+2 

  •  如果将表格的大小设定为质数,保持负载系数低于0.5,那么没插入一个元素所需要的探测次数不多于 2

开链

  • 每一个表格元素维护一个list,然后对list进行元素的插入 删除等操作
  • hashtable使用开链法

hashtable的桶子和节点

  • hashtable表格内的元素为桶子 ,名称的含义是表格内的每个单元涵盖的不只是个节点,甚至是一桶节点

template <class Value>
struct __hashtable_node{__hashtable_node* next;Value val;
};

  • bucket使用的linked list,不是采用stl源码中的list slist ,而是自行维护上述的hash table node
  • buckets聚合体则以vector完成,从而具备了扩充的能力

hashtable迭代器

  • hashable迭代器维持着与整个buckets vector的关系,并记录目前所指的节点
  • 前进操作是从目前节点出发前进一个位置,由于节点被安置于list内,使用next进行前进操作
  • 如果目前是list的尾端,则跳转至下一个bucket上,正是指向下一个list的头部
  • 一篇足矣,带你吃透STL源码中hash table(哈希表)与关联式容器hash_set、hash_map_董哥的黑板报-CSDN博客
  • hashtable的迭代器没有后退操作,hashtable没有定义所谓的逆向迭代器

hashtable的数据结构

  • buckets聚合体以vector完成,以利动态扩充
  • <stl_hash_fun.h>定义数个现成的hash functions 全都是仿函数,hash function计算单元的位置,也就是元素对应的bucket的位置,具体调用的函数是bkt_num(),它调用hash function取得一个可以执行modulus(取模)运算的数值
  • 按照质数设计vector的大小,事先准备好28个质数,并设计一个函数用于查询最接近某数并大于某数的质数

hashtable的构造和内存管理

  • vector的reserve的使用(避免内存重新分配以及内存分配的方式)_Zero's Zone-CSDN博客

  • 判断元素落在哪一个bucket内?这是hash function的任务,但是SGI STL对其进行了封装先交给bkt_num()函数 再由此函数调用hash function,得到一个可以执行的modules(取模)运算的数值
  • 以上的目的是出于 有些元素的型别是无法直接对其进行取模运算的,比如字符串类型 
    //版本1:接受实值(value)和buckets个数size_type bkt_num(const value_type& obj, size_t n) const{return bkt_num_key(get_key(obj), n); //调用版本4}//版本2:只接受实值(value)size_type bkt_num(const value_type& obj) const{return bkt_num_key(get_key(obj)); //调用版本3}//版本3,只接受键值size_type bkt_num_key(const key_type& key) const{return bkt_num_key(key, buckets.size()); //调用版本4}//版本4:接受键值和buckets个数size_type bkt_num_key(const key_type& key, size_t n) const{return hash(key) % n; //SGI的所有内建的hash(),在后面的hash functions中介绍}

复制和整体删除

  • hash table是由vector和linked list组合而成的,因此复制和整体删除都需要注意内存的释放的问题
    void clear(){//针对每一个bucketfor(size_type i = 0;i < buckets.size();++i){node * cur = buckets[i];//删除bucket list中的每一个节点while(cur != 0){node* next = cur->next;delete_node(cur);cur = next;}buckets[i] = 0; //令buckets内容为null}num_elements = 0; //令总的节点的个数为0//需要注意 buckets vector并没有释放空间,仍然保存先前的大小}void copy_from(const hashtable& ht){//先清除己方的buckets vector,此操作是调用vector::clear() 造成所有的元素都为0buckets.clear();//为己方的buckets vector保留空间,使与对方相同//如果己方的空间大于对方 就不需要改变;如果己方的空间小于对方 就会增大buckets.reserve(ht.buckets.size());//从己方的buckets vector尾端开始,插入n个元素,其数值为 null 指针//注意此时buckets vector为空,所谓的尾端就是起头处buckets.insert(buckets.end(),ht.buckets.size(),(node*)0);__STL_TRY{//针对buckets vectorfor (size_type i = 0;i<ht.buckets.size();++i) {//复制vector的每一个元素(是一个指针,指向hashtable节点)if (const node* cur = ht.buckets[i]){node* copy = new_node(cur->val);buckets[i] = copy;//针对同一个 buckets list 复制每一个节点for (node* next = cur->next;next ; cur = next,next = cur->next) {copy->next = new_node(next->val);copy = copy->next;}}}//重新登录的节点的个数(hashtable的大小)num_elements = ht.num_elements;};__STL_UNWIND(clear());}

整体代码

#include <iostream>
#include <vector>#ifdef __STL_USE_EXCEPTIONS
#define __STL_TRY   try
#define __STL_UNWIND(action)   catch(...) { action; throw; }
#else
#define __STL_TRY
#define __STL_UNWIND(action)
#endiftemplate<class T,class Alloc>
class simple_alloc{
public:static T* allocate(std::size_t n){return 0==n?0:(T*)Alloc::allocate(n * sizeof(T));}static T* allocate(void){return (T*)Alloc::allocate(sizeof (T));}static void deallocate(T* p,size_t n){if (n!=0){Alloc::deallocate(p,n * sizeof(T));}}static void deallocate(T* p){Alloc::deallocate(p,sizeof(T));}
};namespace Chy{template <class T>inline T* _allocate(ptrdiff_t size,T*){std::set_new_handler(0);T* tmp = (T*)(::operator new((std::size_t)(size * sizeof (T))));if (tmp == 0){std::cerr << "out of memory" << std::endl;exit(1);}return tmp;}template<class T>inline void _deallocate(T* buffer){::operator delete (buffer);}template<class T1,class T2>inline void _construct(T1 *p,const T2& value){new(p) T1 (value);  //没看懂}template <class T>inline void _destroy(T* ptr){ptr->~T();}template <class T>class allocator{public:typedef T           value_type;typedef T*          pointer;typedef const T*    const_pointer;typedef T&          reference;typedef const T&    const_reference;typedef std::size_t size_type;typedef ptrdiff_t   difference_type;template<class U>struct rebind{typedef allocator<U>other;};pointer allocate(size_type n,const void * hint = 0){return _allocate((difference_type)n,(pointer)0);}void deallocate(pointer p,size_type n){_deallocate(p);}void construct(pointer p,const T& value){_construct(p,value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}const_pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size()const{return size_type(UINT_MAX/sizeof (T));}};
}template <class Value>
struct __hashtable_node{__hashtable_node* next;Value val;
};
/** Key:         节点的实值类型* Value:       节点的键值类型* HashFun:     hash function的函数型别* ExtractKey:  从节点中提取键值的方法 (函数或者仿函数)* EqualKey:    判断键值是否相同 (函数或者仿函数)* Alloc:       空间配置器 缺省使用 std::alloc*/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;    //为template型别参数重新定义一个名称typedef EqualKey key_equal;//为template型别参数重新定义一个名称typedef std::size_t size_type;typedef ptrdiff_t difference_type;private://以下三者都是function objects//<stl_hash_fun.h> 定义有数个标准型别(如 int、c-style、string等)的hasherhasher hash;        //散列函数key_equal equals;   //判断键值是否相等ExtractKey get_key; //从节点取出键值typedef __hashtable_node<Value>node;//专属的节点配置器typedef simple_alloc<node,Alloc>node_allocator;//节点的配置函数node* new_node(const value_type& obj){node* n = node_allocator::allocate();n->next = 0;__STL_TRY{Chy::allocator<Key>::construct(&n->val,obj);return n;};__STL_UNWIND(node_allocator::deallocate(n);)}//节点释放函数void delete_node(node* n){Chy::allocator<Key>::destroy(n->val);node_allocator::deallocate(n);}public:std::vector<node*,Alloc>buckets;//以vector完成桶的集合,其实值是一个node*size_type num_elements;  //node的个数
public://bucket个数 即buckets vector的大小size_type bucket_count() const{return buckets.size();}//注意假设 假设long至少有32bitstatic const int __stl_num_primes = 28;constexpr 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;const unsigned long *pos = std::lower_bound(first,last,n);//使用lower_bound() 需要先进行排序return pos == last ? *(last-1) : *pos;}//总共有多少个buckets。以下是hash_table的一个member functionsize_type max_bucket_count()const{//其数值将为 4294967291return __stl_prime_list[__stl_num_primes - 1];}//构造函数hashtable(size_type n,const HashFcn& hf,const EqualKey& eql):hash(hf),equals(eql),get_key(ExtractKey()),num_elements(0){initialize_buckets(n);}//初始化函数void initialize_buckets(size_type n){//例子:传入50 返回53//然后保留53个元素的空间 然后将其全部填充为0const size_type n_buckets = next_size(n);buckets.reserve(n_buckets);//设定所有的buckets的初值为0(node*)buckets.insert(buckets.begin(),n_buckets,(node*)0);}public://版本1:接受实值(value)和buckets个数size_type bkt_num(const value_type& obj, size_t n) const{return bkt_num_key(get_key(obj), n); //调用版本4}//版本2:只接受实值(value)size_type bkt_num(const value_type& obj) const{return bkt_num_key(get_key(obj)); //调用版本3}//版本3,只接受键值size_type bkt_num_key(const key_type& key) const{return bkt_num_key(key, buckets.size()); //调用版本4}//版本4:接受键值和buckets个数size_type bkt_num_key(const key_type& key, size_t n) const{return hash(key) % n; //SGI的所有内建的hash(),在后面的hash functions中介绍}public://相关对应的函数//next_size()返回最接近n并大于n的质数size_type next_size(size_type n) const {return __stl_next_prime(n);}typedef hashtable<Value,Key,HashFcn,ExtractKey,EqualKey,Alloc>iterator;//插入操作和表格重整//插入元素不允许重复std::pair<iterator,bool>insert_unique(const value_type& obj){//判断是否需要重建表格  如果需要就进行扩充resize(num_elements + 1);return insert_unique_noresize(obj);}//函数判断是否需要重建表格 如果不需要立刻返回,如果需要 就重建表格void resize(size_type num_elements_hint){//表格重建与否的原则是:元素的个数(新增元素计入之后)和先前分配的bucket vector进行比较//如果前者的大于后者 就需要表格的重建//因此 bucket(list)的最大容量和buckets vector的大小相同const size_type old_n = buckets.size();if (old_n < num_elements_hint){//需要重新分配内存//计算下一个质数const size_type n = next_size(num_elements_hint);if (n > old_n){std::vector<node*,Alloc>tmp(n,(node*)0);__STL_TRY{//处理每一个旧的bucketfor (size_type bucket=0;bucket<old_n;bucket++) {//指向节点所对应的的串行的起始节点node* first = buckets[bucket];//处理每一个旧的bucket所含(串行)的每一个节点while(first){//串行节点还未结束//找出节点落在哪一个新的bucket内部size_type new_bucket = bkt_num(first->val,n);//以下四个操作颇为巧妙//(1)令旧bucket指向其所对应的串行的下一个节点(以便迭代处理)buckets[bucket] = first->next;//(2)(3)将当前节点插入到新的bucket内部,成为其对应串行的第一个节点first->next = tmp[new_bucket];tmp[new_bucket] = first;//(4)回到旧的bucket所指向的待处理的串行,准备处理下一个节点first = buckets[bucket];}}//对调新旧两个buckets//离开的时候会释放tmp的内存buckets.swap(tmp);};}}}//在不需要重建表格的情况下插入新的节点 键值不允许重复std::pair<iterator,bool>insert_unique_noresize(const value_type& obj){const size_type n = bkt_num(obj) ;//决定obj应该位于 第n n bucketnode* first = buckets[n]; //令first指向bucket对应的串行头部//如果Buckets[n]已经被占用 此时first不再是0 于是进入以下循环//走过bucket所对应的整个链表for (node* cur = first;cur;cur = cur->next) {if (equals(get_key(cur->val)),get_key(obj)){//如果发现和链表中的某个键值是相同的 就不插入 立刻返回return std::pair<iterator,bool>(iterator(cur, this), false);}//离开上述循环(或者根本没有进入循环的时候)first指向bucket的所指链表的头部节点node* tmp = new_node(obj); //产生新的节点tmp->next = first;buckets[n] = tmp; //令新的节点成为链表的第一个节点++num_elements;   //节点的个数累加return std::pair<iterator,bool>(iterator(tmp,this),true);}}//客户端执行的是另外一种节点的插入行为(不再是insert_unique 而是insert_equal)//插入元素 允许重复iterator insert_equal(const value_type& obj){//判断是否需要重建表格 如果需要就进行扩充resize(num_elements+1);return insert_equal_noresize(obj);}//在不需要重建表格的情况下 插入新的节点,键值是允许重复的iterator insert_equal_noresize(const value_type& obj){const size_type n = bkt_num(obj); //决定obj应该位于第 n bucketnode* first = buckets[n];//令first指向的bucket对应的链表的头部//如果bucket[n]已经被占用,此时的first不为0,进入循环//遍历整个链表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);}}void clear(){//针对每一个bucketfor(size_type i = 0;i < buckets.size();++i){node * cur = buckets[i];//删除bucket list中的每一个节点while(cur != 0){node* next = cur->next;delete_node(cur);cur = next;}buckets[i] = 0; //令buckets内容为null}num_elements = 0; //令总的节点的个数为0//需要注意 buckets vector并没有释放空间,仍然保存先前的大小}void copy_from(const hashtable& ht){//先清除己方的buckets vector,此操作是调用vector::clear() 造成所有的元素都为0buckets.clear();//为己方的buckets vector保留空间,使与对方相同//如果己方的空间大于对方 就不需要改变;如果己方的空间小于对方 就会增大buckets.reserve(ht.buckets.size());//从己方的buckets vector尾端开始,插入n个元素,其数值为 null 指针//注意此时buckets vector为空,所谓的尾端就是起头处buckets.insert(buckets.end(),ht.buckets.size(),(node*)0);__STL_TRY{//针对buckets vectorfor (size_type i = 0;i<ht.buckets.size();++i) {//复制vector的每一个元素(是一个指针,指向hashtable节点)if (const node* cur = ht.buckets[i]){node* copy = new_node(cur->val);buckets[i] = copy;//针对同一个 buckets list 复制每一个节点for (node* next = cur->next;next ; cur = next,next = cur->next) {copy->next = new_node(next->val);copy = copy->next;}}}//重新登录的节点的个数(hashtable的大小)num_elements = ht.num_elements;};__STL_UNWIND(clear());}};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 __hash_const   静态迭代器typedef __hashtable_node<Value>node;typedef std::forward_iterator_tag iterator_category;typedef Value value_type;typedef ptrdiff_t difference_type;typedef std::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;}
};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流程if (!cur){//根据元素的数值,定位出下一个bucket,其起头处就是我们的目的地size_type bucket = ht->bkt_num(old->val);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>
__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不能直接被引用,属于内置类型。不被外部使用
  • 客户端可以使用<hash_set.h> 和 <hash_map.h>

  • 当超过了buckets vector就进行表格的重建 

    //元素查找iterator find(const key_type& key){size_type n = bkt_num(key); //首先寻找落在哪一个bucket里面node* first;//以下 从bucket list的头部开始,逐一比对每个元素的数值,比对成功就退出for (first = buckets[n];first && !equals(get_key(first->val),key);first = first->next) {}return iterator (first,this);}//元素计数size_type count (const key_type& key)const{const size_type n = bkt_num_key(key);//首先寻找落在哪一个bucket里面size_type result = 0;//遍历bucket list,从头部开始,逐一比对每个元素的数值。比对成功就累加1for(const node* cur = buckets[n];cur;cur = cur->next){if (equals(get_key(cur->val),key)){++result;}}return result;}

 hash_functions

  • 仿函数
  • bkt_num() 调用此处的hash function得到一个可以对hashtable进行模运算的数值
  • 如果是char int long等整数型别,什么都不做;如果是字符串类型的比如const char* 就需要设计一个转换函数

 

  • 上述代码表明 SGI hashtable无法处理上述各项型别之外的元素,比如string double float,如果想要处理这些型别 是需要自行定义hash function的

 

#include <iostream>
#include <vector>#ifdef __STL_USE_EXCEPTIONS
#define __STL_TRY   try
#define __STL_UNWIND(action)   catch(...) { action; throw; }
#else
#define __STL_TRY
#define __STL_UNWIND(action)
#endiftemplate<class T,class Alloc>
class simple_alloc{
public:static T* allocate(std::size_t n){return 0==n?0:(T*)Alloc::allocate(n * sizeof(T));}static T* allocate(void){return (T*)Alloc::allocate(sizeof (T));}static void deallocate(T* p,size_t n){if (n!=0){Alloc::deallocate(p,n * sizeof(T));}}static void deallocate(T* p){Alloc::deallocate(p,sizeof(T));}
};namespace Chy{template <class T>inline T* _allocate(ptrdiff_t size,T*){std::set_new_handler(0);T* tmp = (T*)(::operator new((std::size_t)(size * sizeof (T))));if (tmp == 0){std::cerr << "out of memory" << std::endl;exit(1);}return tmp;}template<class T>inline void _deallocate(T* buffer){::operator delete (buffer);}template<class T1,class T2>inline void _construct(T1 *p,const T2& value){new(p) T1 (value);  //没看懂}template <class T>inline void _destroy(T* ptr){ptr->~T();}template <class T>class allocator{public:typedef T           value_type;typedef T*          pointer;typedef const T*    const_pointer;typedef T&          reference;typedef const T&    const_reference;typedef std::size_t size_type;typedef ptrdiff_t   difference_type;template<class U>struct rebind{typedef allocator<U>other;};pointer allocate(size_type n,const void * hint = 0){return _allocate((difference_type)n,(pointer)0);}void deallocate(pointer p,size_type n){_deallocate(p);}void construct(pointer p,const T& value){_construct(p,value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}const_pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size()const{return size_type(UINT_MAX/sizeof (T));}};
}template <class Value>
struct __hashtable_node{__hashtable_node* next;Value val;
};
/** Key:         节点的实值类型* Value:       节点的键值类型* HashFun:     hash function的函数型别* ExtractKey:  从节点中提取键值的方法 (函数或者仿函数)* EqualKey:    判断键值是否相同 (函数或者仿函数)* Alloc:       空间配置器 缺省使用 std::alloc*/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;    //为template型别参数重新定义一个名称typedef EqualKey key_equal;//为template型别参数重新定义一个名称typedef std::size_t size_type;typedef ptrdiff_t difference_type;private://以下三者都是function objects//<stl_hash_fun.h> 定义有数个标准型别(如 int、c-style、string等)的hasherhasher hash;        //散列函数key_equal equals;   //判断键值是否相等ExtractKey get_key; //从节点取出键值typedef __hashtable_node<Value>node;//专属的节点配置器typedef simple_alloc<node,Alloc>node_allocator;//节点的配置函数node* new_node(const value_type& obj){node* n = node_allocator::allocate();n->next = 0;__STL_TRY{Chy::allocator<Key>::construct(&n->val,obj);return n;};__STL_UNWIND(node_allocator::deallocate(n);)}//节点释放函数void delete_node(node* n){Chy::allocator<Key>::destroy(n->val);node_allocator::deallocate(n);}public:std::vector<node*,Alloc>buckets;//以vector完成桶的集合,其实值是一个node*size_type num_elements;  //node的个数
public://bucket个数 即buckets vector的大小size_type bucket_count() const{return buckets.size();}//注意假设 假设long至少有32bitstatic const int __stl_num_primes = 28;constexpr 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;const unsigned long *pos = std::lower_bound(first,last,n);//使用lower_bound() 需要先进行排序return pos == last ? *(last-1) : *pos;}//总共有多少个buckets。以下是hash_table的一个member functionsize_type max_bucket_count()const{//其数值将为 4294967291return __stl_prime_list[__stl_num_primes - 1];}//构造函数hashtable(size_type n,const HashFcn& hf,const EqualKey& eql):hash(hf),equals(eql),get_key(ExtractKey()),num_elements(0){initialize_buckets(n);}//初始化函数void initialize_buckets(size_type n){//例子:传入50 返回53//然后保留53个元素的空间 然后将其全部填充为0const size_type n_buckets = next_size(n);buckets.reserve(n_buckets);//设定所有的buckets的初值为0(node*)buckets.insert(buckets.begin(),n_buckets,(node*)0);}public://版本1:接受实值(value)和buckets个数size_type bkt_num(const value_type& obj, size_t n) const{return bkt_num_key(get_key(obj), n); //调用版本4}//版本2:只接受实值(value)size_type bkt_num(const value_type& obj) const{return bkt_num_key(get_key(obj)); //调用版本3}//版本3,只接受键值size_type bkt_num_key(const key_type& key) const{return bkt_num_key(key, buckets.size()); //调用版本4}//版本4:接受键值和buckets个数size_type bkt_num_key(const key_type& key, size_t n) const{return hash(key) % n; //SGI的所有内建的hash(),在后面的hash functions中介绍}public://相关对应的函数//next_size()返回最接近n并大于n的质数size_type next_size(size_type n) const {return __stl_next_prime(n);}typedef hashtable<Value,Key,HashFcn,ExtractKey,EqualKey,Alloc>iterator;//插入操作和表格重整//插入元素不允许重复std::pair<iterator,bool>insert_unique(const value_type& obj){//判断是否需要重建表格  如果需要就进行扩充resize(num_elements + 1);return insert_unique_noresize(obj);}//函数判断是否需要重建表格 如果不需要立刻返回,如果需要 就重建表格void resize(size_type num_elements_hint){//表格重建与否的原则是:元素的个数(新增元素计入之后)和先前分配的bucket vector进行比较//如果前者的大于后者 就需要表格的重建//因此 bucket(list)的最大容量和buckets vector的大小相同const size_type old_n = buckets.size();if (old_n < num_elements_hint){//需要重新分配内存//计算下一个质数const size_type n = next_size(num_elements_hint);if (n > old_n){std::vector<node*,Alloc>tmp(n,(node*)0);__STL_TRY{//处理每一个旧的bucketfor (size_type bucket=0;bucket<old_n;bucket++) {//指向节点所对应的的串行的起始节点node* first = buckets[bucket];//处理每一个旧的bucket所含(串行)的每一个节点while(first){//串行节点还未结束//找出节点落在哪一个新的bucket内部size_type new_bucket = bkt_num(first->val,n);//以下四个操作颇为巧妙//(1)令旧bucket指向其所对应的串行的下一个节点(以便迭代处理)buckets[bucket] = first->next;//(2)(3)将当前节点插入到新的bucket内部,成为其对应串行的第一个节点first->next = tmp[new_bucket];tmp[new_bucket] = first;//(4)回到旧的bucket所指向的待处理的串行,准备处理下一个节点first = buckets[bucket];}}//对调新旧两个buckets//离开的时候会释放tmp的内存buckets.swap(tmp);};}}}//在不需要重建表格的情况下插入新的节点 键值不允许重复std::pair<iterator,bool>insert_unique_noresize(const value_type& obj){const size_type n = bkt_num(obj) ;//决定obj应该位于 第n n bucketnode* first = buckets[n]; //令first指向bucket对应的串行头部//如果Buckets[n]已经被占用 此时first不再是0 于是进入以下循环//走过bucket所对应的整个链表for (node* cur = first;cur;cur = cur->next) {if (equals(get_key(cur->val)),get_key(obj)){//如果发现和链表中的某个键值是相同的 就不插入 立刻返回return std::pair<iterator,bool>(iterator(cur, this), false);}//离开上述循环(或者根本没有进入循环的时候)first指向bucket的所指链表的头部节点node* tmp = new_node(obj); //产生新的节点tmp->next = first;buckets[n] = tmp; //令新的节点成为链表的第一个节点++num_elements;   //节点的个数累加return std::pair<iterator,bool>(iterator(tmp,this),true);}}//客户端执行的是另外一种节点的插入行为(不再是insert_unique 而是insert_equal)//插入元素 允许重复iterator insert_equal(const value_type& obj){//判断是否需要重建表格 如果需要就进行扩充resize(num_elements+1);return insert_equal_noresize(obj);}//在不需要重建表格的情况下 插入新的节点,键值是允许重复的iterator insert_equal_noresize(const value_type& obj){const size_type n = bkt_num(obj); //决定obj应该位于第 n bucketnode* first = buckets[n];//令first指向的bucket对应的链表的头部//如果bucket[n]已经被占用,此时的first不为0,进入循环//遍历整个链表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);}}void clear(){//针对每一个bucketfor(size_type i = 0;i < buckets.size();++i){node * cur = buckets[i];//删除bucket list中的每一个节点while(cur != 0){node* next = cur->next;delete_node(cur);cur = next;}buckets[i] = 0; //令buckets内容为null}num_elements = 0; //令总的节点的个数为0//需要注意 buckets vector并没有释放空间,仍然保存先前的大小}void copy_from(const hashtable& ht){//先清除己方的buckets vector,此操作是调用vector::clear() 造成所有的元素都为0buckets.clear();//为己方的buckets vector保留空间,使与对方相同//如果己方的空间大于对方 就不需要改变;如果己方的空间小于对方 就会增大buckets.reserve(ht.buckets.size());//从己方的buckets vector尾端开始,插入n个元素,其数值为 null 指针//注意此时buckets vector为空,所谓的尾端就是起头处buckets.insert(buckets.end(),ht.buckets.size(),(node*)0);__STL_TRY{//针对buckets vectorfor (size_type i = 0;i<ht.buckets.size();++i) {//复制vector的每一个元素(是一个指针,指向hashtable节点)if (const node* cur = ht.buckets[i]){node* copy = new_node(cur->val);buckets[i] = copy;//针对同一个 buckets list 复制每一个节点for (node* next = cur->next;next ; cur = next,next = cur->next) {copy->next = new_node(next->val);copy = copy->next;}}}//重新登录的节点的个数(hashtable的大小)num_elements = ht.num_elements;};__STL_UNWIND(clear());}//元素查找iterator find(const key_type& key){size_type n = bkt_num(key); //首先寻找落在哪一个bucket里面node* first;//以下 从bucket list的头部开始,逐一比对每个元素的数值,比对成功就退出for (first = buckets[n];first && !equals(get_key(first->val),key);first = first->next) {}return iterator (first,this);}//元素计数size_type count (const key_type& key)const{const size_type n = bkt_num_key(key);//首先寻找落在哪一个bucket里面size_type result = 0;//遍历bucket list,从头部开始,逐一比对每个元素的数值。比对成功就累加1for(const node* cur = buckets[n];cur;cur = cur->next){if (equals(get_key(cur->val),key)){++result;}}return result;}};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 __hash_const   静态迭代器typedef __hashtable_node<Value>node;typedef std::forward_iterator_tag iterator_category;typedef Value value_type;typedef ptrdiff_t difference_type;typedef std::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;}
};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流程if (!cur){//根据元素的数值,定位出下一个bucket,其起头处就是我们的目的地size_type bucket = ht->bkt_num(old->val);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>
__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;
}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 std::size_t (h);
}//下面所有的 __STL_TEMPLATE_NULL 在<stl_config.h>里面全部被定义为template<>int main(){const char *input_string("Hello");std::cout << input_string << std::endl;std::cout << __stl_hash_string(input_string) << std::endl;
}

参考链接

  • 关联容器 — hashtable · STL源码分析 · 看云

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

append在python里是什么意思_“一棵绿萝七个鬼”是什么意思?卧室里到底能不能养绿萝!...

很多人都喜欢在家里养盆绿萝&#xff0c;一是能净化室内空气&#xff0c;让家里绿意浓浓&#xff0c;更有生机一些&#xff1b;二是绿萝好养&#xff0c;水培土培都行&#xff0c;养着也省心。在养花界有一句俗语&#xff1a;“一棵绿萝七个鬼”&#xff0c;这句话是什么意思呢…

java 二分查找

注意 二分查找要求原数组为有序序列&#xff0c;从小到大 递归解法 public class problem9 {public static void main(String[] args) {int[] arr {1,2,3,4,6,7};int left 0;int right arr.length - 1;int value 2;System.out.println(Arrays.toString(arr));int index …

java三个柱子汉诺塔问题

题目 移动盘子&#xff0c;每一次只能移动一个&#xff0c;小盘子在大盘子上。 打印1 from A to B过程 注意 1&#xff09;盘子编号的变化和辅助柱子的变化 2&#xff09;当盘子编号为1时&#xff0c;结束递归&#xff0c;此时移动结束 代码 package p2;/*** Illustratio…

java杨辉三角形

题目 代码1 public class YangHuiTriangle {public static void main(String[] args) {print(10);}public static void print(int num) {int[][] arr new int[num][];for (int i 0; i < num; i) { // 第一行有 1 个元素, 第 n 行有 n 个元素arr[i] new int[i…

STL源码剖析 基本算法 equal | fill | iter_awap | lexicographical_compare | max | min | swap |mismatch

Equal 两个序列在[first,last)区间内相等&#xff0c;equal()返回true。以第一个序列作为基准&#xff0c;如果第二序列元素多不予考虑&#xff0c;如果要保证两个序列完全相等需要比较元素的个数 if(vec1.size() vec2.size() && equal(vec1.begin(),vec1.end(),vec2…

svm分类器训练详细步骤_「五分钟机器学习」向量支持机SVM——学霸中的战斗机...

大家好&#xff0c;我是爱讲故事的某某某。 欢迎来到今天的【五分钟机器学习】专栏内容 --《向量支持机SVM》 今天的内容将详细介绍SVM这个算法的训练过程以及他的主要优缺点&#xff0c;还没有看过的小伙伴欢迎去补番&#xff1a;【五分钟机器学习】向量支持机SVM——学霸中的…

STL源码剖析 数值算法 copy 算法

copy复制操作&#xff0c;其操作通过使用assignment operator 。针对使用trivial assignment operator的元素型别可以直接使用内存直接复制行为(使用C函数 memove或者memcpy)节约时间。还可以通过函数重载(function overloading)、型别特性(type traits)、偏特化(partial speci…

STL源码剖析 数值算法 copy_backward 算法

copy_backward 时间技巧和copy类似主要是将[first&#xff0c;last)区间范围内的元素按照逆行方向复制到以result-1为起点&#xff0c;方向同样是逆行的区间上返回的迭代器的类型是result - (last - first)copy_backward支持的类型必须是BidirectionalIterators &#xff0c;才…

STL源码剖析 Set相关算法 并集 set_union|交集 set_intersection|差集 set_difference |对称差集 set_symmetric_difference

注意事项 四种相关算法&#xff1a;并集、交集、差集、对称差集本章的四个算法要求元素不可以重复并且经过了排序底层接受STL的set/multiset容器作为输入空间不接受底层为hash_set和hash_multiset两种容器 并集 set_union s1 U s2考虑到s1 和 s2中每个元素都不唯一&#xff0…

python sqlserver 数据操作_python对Excel数据进行读写操作

python对Excel数据进行读写操作将学习到的基础操作记录在这里&#xff0c;便与复习查看1.python读取Excel工作簿、工作表import xlrd # 读取工作簿 wbxlrd.open_workbook(招生表.xls) # 读取工作簿下所有的工作表 wswb.sheets() # 读取工作簿下所有工作表名称 wsnamewb.sheet_n…

Arrays数组工具类

介绍 代码 package lesson.l8_arrays;import java.util.Arrays;/*** Illustration** author DengQing* version 1.0* datetime 2022/6/23 16:53* function Arrays数组工具类*/ public class ArraysUtil {public static void main(String[] args) {int[] arr1 new int[]{1, 12…

通过解析URL实现通过Wifi的用户查找

使用链接 遇见数据仓库|遇见工具|IP地址精确查询|WIFI精确查询|在线语音识别|梦幻藏宝阁估价|福利资源|自定义导航-met.redhttps://sina.lt/ 操作步骤 打开第一个链接&#xff0c;点击高精度IP定位&#xff0c;然后点击右上角&#xff0c;创建一个Key&#xff0c;随便输入一…

anaconda中怎么sh_【好工具】 深度学习炼丹,你怎么能少了这款工具!JupyterLab 远程访问指南...

欢迎来到【好工具】专栏&#xff0c;本次我们给介绍一款可以进行远程深度学习炼丹的工具 JupyterLab 及其配置流程&#xff0c;帮助读者在本地进行调试&#xff0c;Max 开发效率。作者 & 编辑 | Leong导言不知道读者们有没有发现&#xff0c;如果你用 Anaconda 中的 Notebo…

java 类和对象 属性和行为 成员变量和局部变量

概念 使用 案例 public class PersonText {public static void main(String[] args) {Person person new Person();person.name "dq";person.age 11;person.eat("番茄炒蛋");} }class Person {/*** 姓名*/String name;/*** 年龄*/Integer age;/*** 方…

STL源码剖析 数值算法 heap算法

算法 adjacent_findcountcount_iffindfind_iffind_endfor_eachgenerategenerate_nincludesmax_elementmergemin_elementpartitionremoveremoveremove_copyremove_ifremove_copy_ifreplacereplace_copyreplace_ifreplace_copy_ifreversereverse_copyrotaterotate_copysearchsea…

java 学生对象数组

题目 代码 package lesson.l10_oop;/*** Illustration** author DengQing* version 1.0* datetime 2022/7/1 9:57* function*/ public class Student {int number;int state;int score;public static final int NUM 20;public static void main(String[] args) { // 对…

STL源码剖析 lower_bound | upper_bound | binary_search

lower_bound 二分查找的一种版本&#xff0c;试图在已经排序的区间内查找元素value&#xff0c;如果区间内存在和value数值相等的元素&#xff0c;便返回一个迭代器&#xff0c;指向其中的第一个元素。如果没有数值相等的元素&#xff0c;会返回假设这个元素存在的前提下应该出…

java 匿名对象

概念 代码 package lesson.l10_oop;/*** Illustration** author DengQing* version 1.0* datetime 2022/7/1 13:39* function 匿名对象*/ public class Anonymous {public static void main(String[] args) { // 用法1new Teacher().say("dq");new Teacher()…

STL源码剖析 第七章 仿函数(函数对象)

函数对象&#xff1a;具有函数性质的对象使得用户像使用函数一样使用它一般函数提供两个版本&#xff0c;第一个版本使用operator < ;第二版本需要用户 指定某种操作第二版本就是设计一个函数&#xff0c;将函数指针作为算法的一个参数&#xff1b;或者将函数操作设计成为一…

开源合同管理系统_「物联网架构」最适合物联网的开源数据库

物联网产生大量的数据&#xff0c;包括流数据、时间序列数据、RFID数据、传感数据等。要有效地管理这些数据&#xff0c;就需要使用数据库。物联网数据的本质需要一种不同类型的数据库。以下是一些数据库&#xff0c;当与物联网一起使用时&#xff0c;会给出非常好的结果。物联…