SGISTL源码阅读二十三 hashtable下

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是非常注重效率问题的。
接下来我们将要介绍到hashsethashmap,他们的底层容器就是hashtable

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值