stl源码剖析 详细学习笔记 hashtable


//---------------------------15/03/24----------------------------


//hashtable

{

    /*

        概述:

        sgi采用的是开链法完成hashtable的,也就是用链表来存储映射到相同位置的元素。

    */

    

    //node(节点)

    template<class Value>

    struct __hashtable_node

    {

        __hashtable_node* next;

        Value val;

    };

    //bucket维护的linked list不是stllistslist,而是自行维护的,table则是用vector完成以便扩充

   

    

    //迭代器

    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;

        

        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;

        

        __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;}

        

    };

    

    

    //operator++()

    

    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是当前节点,用->next取得下个节点,如果这个位置已经没有更多节点,cur就是null

        cur = cur->next;

        if(!cur)

        {

            //如果没有更多节点,使用val取得之前的curtable中的位置

            size_type bucket = ht->bkt_num(old->val);

            //只要curnull 并且 下一个位置小于最大值 就让cur为下一个位置的节点(如果存在就有值,不然为null)

            while(!cur && ++bucket < ht->buckets.size())

                cur = ht->buckets[bucket];

        }

        //如果++没有找到下一个元素(之后没有元素了)cur就为ht->buckets[buckets.size()-1]==null

        //返回的iterator指向null,否则指向下一个节点

        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;

        return tmp;

    }

    

    //没有--操作

    

    

    //class声明式

    template<class Value, class Key, class HashFcn, class ExtractKey,

            class EqualKey, class Alloc=alloc>

    class hashtable;

    

    //class定义

    //Value:节点的实值类型   Key:节点的键值类型   HashFcnhash function的函数类型

    //ExtractKey: 从节点中取出键值的方法(可以使函数或仿函数) EqualKey:判断键值相同与否的方法(函数或仿函数)

    template<class Value, class Key, class HashFcn, class ExtractKey,

            class EqualKey, class Alloc>

    class hashtable

    {

    public:

        typedef HashFcn hasher;

        typedef EqualKey key_equel;

        typedef size_t size_type;

        

    private:

        hasher  hash;

        key_equel equals;

        ExtractKey get_key;

        

        typedef __hashtable_node<Value> node;

        typedef simple_alloc<node, Alloc> node_allocator;

        

        vector<node*, Alloc> buckets;

        size_type num_elements;

        

    public:

        size_type bucket_count() const {return buckets.size();}

        

        

        

    };

    

    //虽然开链法不要求表格大小必须为质数,但是sgi stl还是用质数大小来设计表格(这样碰撞的机会会变小)

    //而且先把28个质数计算好,以备随时访问,同时提供一个函数来查询最适合的质数

    

    static const int __stl_num_primes = 28;

    static const unsigned long __stl_prime_list[__stl_num_primes]=

    {

        53, 97, 193... 4294967291ul

    };

    

    //获取大于n的下一个质数,不会超过last1

    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;

        //使用全局算法lower_bound获取n的位置,把指针当迭代器来用

        const unsigned long* pos = lower_bound(first, last, n);

        

        return pos ==last ?*(last - 1) : *pos;

    }

    

    size_type max_bucket_count() const

    {

        return __stl_prime_list[__stl_num_primes - 1];

    }

    

    

    //构造与内存管理

    template<class Value, class Key, class HashFcn, class ExtractKey,

    class EqualKey, class Alloc>

    class hashtable

    {

        typedef simple_alloc<node, Alloc> node_allocator;

        

        //创建和销毁节点,和以前类型

        node* new_node(const value_type& obj)

        {

            node* n =node_allocator::allocate();

            n->next = 0;

            __STL_TRY

            {

                construct(&n->val, obj);

                return n;

            }

            __STL_UNWIND(node_allocator::deallocate(n));

        }

        

        void delete_node(node* n)

        {

            destroy(&n->val);

            node_allocator::deallocate(n);

        }

        

        //构造函数,设置好hash函数,判断相等的函数,取得键值(键值是用来比较是否相等的)的函数

        //根据n设置好table的大小

        hashtable(size_type n, const HashFcn& hf, const EqualKey& eql)

        :hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)

        {

            initialize_buckets(n);

        }

        

        //initialize_buckets

        void initialize_buckets(size_type n)

        {

            //找到合适的质数

            const size_type n_buckets = next_size(n);

            //保留 n_buckets大小的空间

            buckets.reserve(n_buckets);

            //所有的空间初始化为0

            buckets.insert(buckets.end(), n_buckets, (node*)0);

            num_elements = 0;

        }

        

        //insert

        

        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);

        }

        

        //取得位置,首先要根据get_key函数 取得键值,只要针对非数值类型的变量

        //比如字符串,string或者自定义类对象(对于这些我们要自己写好get_key函数传入模版参数)

        //n表示取模的大小,最后要先调用hash函数,然后取模(默认就是table的大小)

        size_type bkt_num(const value_type& obj, size_t n) const

        {

            return bkt_num_key(get_key(obj), n);

        }

        //使用默认的取模大小

        size_type bkt_num(const value_type& obj) const

        {

            return bkt_num_key(get_key(obj));

        }

        

        //使用默认的取模大小

        size_type bkt_num_key(const key_type& key) const

        {

            return bkt_num_key(key, buckets.size());

        }

        

        //真正的取位置操作,根据传入的键值,先调用hash函数,然后取模

        size_type bkt_num_key(const value_type& obj, size_t n) const

        {

            return hash(key) % n;

        }

        

        //find

        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)

            {}

            //不存在this就是null,会返回false,否则就是true

            return iterator(first, this);

        }

        

        //count

        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;

        }

        

    };

    

    //resize

    template<class V, class K, class HF, class Ex, class Eq, class A>

    __hashtable_iterator<V, K, HF, Ex, Eq, A>&

    __hashtable_iterator<V, K, HF, Ex, Eq, A>::resize(size_type num_elements_hint)

    {

        

        //纪录原来table的大小

        const size_type old_n = buckets.size();

        if(num_elements_hint > old_n)

        {

            //如果新大小大于原来table大小

            //找到下一个合适的质数

            const size_type n = next_size(num_elements_hint);

            if(n > old_n)

            {

                //这里如果之前n就是最后一个质数了,就不会进入这个块语句了

                //初始化n大小的为0vector

                vector<node*, A> tmp(n, (node*)0);

                for(size_type bucket = 0; bucket < old_n; ++bucket)

                {

                    //从原来的table中一个个复制到新table

                    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];

                    }

                }

                //交换新的table为当前table

                buckets.swap(tmp);

            }

        }

    }

    

    //insert_unique_noresize

    template<class V, class K, class HF, class Ex, class Eq, class A>

    __hashtable_iterator<V, K, HF, Ex, Eq, A>&

    __hashtable_iterator<V, K, HF, Ex, Eq, A>::insert_unique_noresize(const value_type& obj)

    {

        //插入操作不可重复

        //找到位置

        const size_type n =bkt_num(obj);

        //第一个节点

        node* first = buckets[n];

        

        //查找节点,如果有重复的键值,就返回false

        for(node* cur = first; cir; 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_equal_noresize

    template<class V, class K, class HF, class Ex, class Eq, class A>

    __hashtable_iterator<V, K, HF, Ex, Eq, A>&

    __hashtable_iterator<V, K, HF, Ex, Eq, A>::insert_equal_noresize(const value_type& obj)

    {

        //可以插入相同键值的节点,如果有相同键值就插到原来键值的后面,否则成为第一个节点

        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);

    }

    

    //clear

    template<class V, class K, class HF, class Ex, class Eq, class A>

    __hashtable_iterator<V, K, HF, Ex, Eq, A>&

    __hashtable_iterator<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;

    }

    

    

    //copy_from

    template<class V, class K, class HF, class Ex, class Eq, class A>

    __hashtable_iterator<V, K, HF, Ex, Eq, A>&

    __hashtable_iterator<V, K, HF, Ex, Eq, A>::copy_from(const hashtable& ht)

    {

        //调用buckets.clear() 先清除原来的table,再插入要copy的各个节点

        //疑惑: 为什么不先调用一次clear(),不这样 原来table中的节点不是都没释放么?

        //不是会造成内存泄漏?

        buckets.clear();

        buckets.reserve(ht.buckets.size());

        

        bucket.insert(buckets.end(), ht.buckets.size(), (node*) 0 );

        __STL_TRY

        {

            for(size_type i = 0; i < ht.buckets.size(); ++i)

            {

                if(const node* cur = ht.buckets[i])

                {

                    node* copy = new_node(cur->val);

                    bucket[i] = copy;

                    

                    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;

        }

        __STL_UNWIND(clear());

    }

    

    

    template<class Key>

    struct hash{};

    //计算key值函数,计算char*类型的 key值: h(n)=h(n-1)*5 + *s   *s就是(*s)ascii码值

    // 这样计算就使得字符与其位置有关,如果少了h(n-1)*5 那么 asd dsa 就会计算得到相同的位置

    inline size_t __stl_hash_string(const char* s)

    {

        unsigned long h = 0;

        for( ; *s; ++s)

            h = 5*h + *s;

        

        return size_t(h);

    }

    

    //模版特化

    //仿函数:利用 __stl_hash_string 取得key

    __STL_TEMPLATE_NULL struct hash<char*>

    {

        size_t operator()(const char* s) const {return __stl_hash_string(s);}

    };

    

    /*

        总结:hashtable就是一个大小为质数的vector中存放一条链表,查找和存放都要利用hash函数取得

        对应的位置,然后存放到链表中,hash函数可以自定义(自己针对自己的类型进行特化)hashtablehash

        函数正确来说其实就是取模操作(默认操作),上面说的hash函数只是给出一个键值(当然也可以在这里自定义hash函数,

        比如加个质数什么的),最后当然还是要取模运算。

        最后就是copy_from里为什么没有调用clear(),这样应该会造成内存泄漏,当然很可能是我想错了,毕竟这是大牛

        们写的 我也查过最新的sgi stl 上面还是这么写的。

     

    */

}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值