SGISTL源码探究-STL中的hashtable(上)

《STL源码剖析》之hashtable hashtable即散列表,也叫哈希表,它对元素的插入、删除和访问操作具有常数时间复杂度的表现,这种表现不依赖于输入元素的随机性。 阅读详情

前言

关于哈希表部分,哈希函数以及解决哈希冲突的方法都有很多种,在本文中不会详细去讨论哈希表的大量知识,而是以SGISTL实现的hashtable为主,所以如果对hashtable不太了解,建议先去网上查阅相关的资料。这里只给出基本的概念。

了解哈希表原理

基本概念

哈希表(hashtable,也称散列表):根据key来访问元素的一种数据结构。它可能先经过哈希函数计算出key,然后通过key访问表中对应key的位置的元素,这样的查找速度很快。
下面这张图是维基百科给出的定义:

其实关于hashtable最重要的无非就两样东西:一个是哈希函数、另一个是处理哈希冲突的方法。

哈希函数

目前常用的哈希函数有以下几种方法:
1. 直接定址法:取关键字或者关键字的某个线性函数为散列地址,比如hash(k) = k或hash(k) = a * k + b,a、b为常数。
2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
3. 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
5. 随机数法
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即hash(k) = k mod p,p <= m。不仅可以对关键字直接取模,也可在折叠法、平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生冲突。

解决冲突的方法

当两个不同的关键字通过哈希函数计算出来的键相同时,此时就产生了哈希冲突,需要采取一些措施来解决这种现象。
1. 开放寻址法:hash(i) = (hash(key) + d_i) mod m,i = 1,2…k(k <= m - 1),hash(key)为哈希函数,m为哈希表的长度,d_i为增量序列,i为发生冲突的次数。关于增量序列,也有它自己的算法。
- 每次和冲突的次数相等或者是一个线性函数,这种呈线性递增的被称为线性探测。
- 如果对i进行平方求值然后相加,这种称为平方探测。或者使用伪随机数序列,称为伪随机探测。
- 还有一种再哈希,即二次探测,采用h(k,i) = (h(k) + c_1*i + c_2*i^2) mod m,h(k)是辅助哈希函数,c1和c2为正的辅助常数。
- 关于开放寻址法的方法确实很多,双哈希 算是比较好的方法之一,它采用h(k, i) = (h_1(k) + i*h_2(k)) mode m这样的哈希函数。

当探测到了一个空的单元时,就会把散列地址存放在该单元。

当时当线性探测/平方探测这种方法时,可能会导致聚集现象,即可能会导致哈希表中某一块区域占满了,但是其他处于空余状态,如果造成这种现象了,会导致冲突大量发生,造成性能的损失。
2. 链接法(叫法可能有所不同):这种做法很简单,当出现哈希冲突时,将该元素与其他会哈希到该位置的元素形成一个链表即可。

查找效率

关于哈希表的效率问题,我们应当尽量采用足够合适的哈希函数来避免冲突,如果冲突了,我们也应该使用合适的解决冲突的方法。如果造成了聚集现象,会导致性能的严重下降,甚至会选择重建哈希表。
考虑的因素除了通过哈希函数产生的元素是否均匀以及解决哈希冲突的方法的优劣之外,还有一个是装载因子(load factor),定义为填入表的元素个数/哈希表的长度,当装载因子越大,证明产生冲突的可能性就越大。

以上便是关于哈希部分的一个大致的说明,参考了维基百科里面的一些资料,关于哈希还有很多地方没有说到,比如完美哈希(无冲突)等。现在已经验证了有几个哈希函数能取得比较好的性能,感兴趣的可以去网上查查。

STL中的hashtable

以上是关于哈希表的概念,接下来我们来看看STL中是如何实现哈希表的。

引入

首先,stl中实现哈希函数部分是放在stl_hash_fun.h文件里的,供hashtable调用。
其次,关于解决哈希冲突的方法,stl中采用的是链接法。
最后,它的大致实现是通过vector容器存储哈希函数映射前的元素,并且通过哈希映射到重复的位置的元素将形成链表,而vector上每个单元称作bucket(桶的意思),因为可能不止一个节点,可能是一条链表。
接下来我们就进入到它的源码部分(迭代器、相关的定义及数据结构、构造/析构函数、常用操作)。

迭代器
#include <stl_algobase.h>
#include <stl_alloc.h>
#include <stl_construct.h>
#include <stl_tempbuf.h>
#include <stl_algo.h>
#include <stl_uninitialized.h>
#include <stl_function.h>
#include <stl_vector.h>
#include <stl_hash_fun.h>

__STL_BEGIN_NAMESPACE

//链表的节点,当有元素哈希到同一个位置上时,会形成链表
template <class Value>
struct __hashtable_node
{
  __hashtable_node* next;
  Value val;
};  

template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey, class Alloc = alloc>
class hashtable;

template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator;

template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_const_iterator;

//迭代器部分
template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
  /* 首先我们先来看看对应的模板参数代表什么
   * Value:值的类型
   * Key:键的类型
   * HashFcn:哈希函数的函数型别(对象)
   * ExtractKey:从节点取出键的方法(对象)
   * EqualKey:判断键是否相同的方法(对象)
   * Alloc:空间配置器
   * 关于函数对象,它其实就是重载了()操作符,之后便可以像函数指针一样使用了,并且可以传递附加数据
   */
  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;

  //声明该迭代器的五种相应型别以及difference_type(如果忘了建议翻到迭代器部分再看看)
  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;
  /* 迭代器指向的节点所属的容器节点
   * 因为vector上存储的可能是一个链表,所以指向了链表的节点还不够
   * 还需要知道是哪个bucket,因为涉及到跳bucket的情况
   * 这个时候就需要知道迭代器指向是哪个容器,所以需要hashtable* ht这个变量
   */
  hashtable* ht;

  /* 构造函数,指名当前指向的节点,以及指向的hashtable */
  __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; }
};
/* 以下是const_iterator部分
 * 和上面的iterator实现类似
 * 只是内部的数据成员换成了const之类的改变
 */
template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_const_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 const Value& reference;
  typedef const Value* pointer;

  //将迭代器指向的节点以及bucket定义成常量
  const node* cur;
  const hashtable* ht;

  /* 构造函数部分,多了个拷贝构造函数 */
  __hashtable_const_iterator(const node* n, const hashtable* tab)
    : cur(n), ht(tab) {}
  __hashtable_const_iterator() {}
  __hashtable_const_iterator(const iterator& it) : cur(it.cur), ht(it.ht) {}
  reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
  const_iterator& operator++();
  const_iterator operator++(int);
  bool operator==(const const_iterator& it) const { return cur == it.cur; }
  bool operator!=(const const_iterator& it) const { return cur != it.cur; }
};
定义部分及数据结构
// Note: assumes long is at least 32 bits.
/* STL要求vector的大小为质数,于是先预置了28个质数
 * 当我们创建的hashtable的大小为50时,它会向上选择最靠近的质数作为容量大小
 */
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, 3221225473ul, 4294967291ul
};
/* 调整n,将其调整成最接近上面数组中的某个质数的值(注意只会向上取或者不变,不可能缩小) */
inline unsigned long __stl_next_prime(unsigned long n)
{
  //指向__stl_prime_list数组起始元素
  const unsigned long* first = __stl_prime_list;
  //指向__stl_prime_list数组的末尾元素后一个位置
  const unsigned long* last = __stl_prime_list + __stl_num_primes;
  //lower_bound的作用在[first, last)范围内寻找第一个比n大的元素并返回其迭代器
  const unsigned long* pos = lower_bound(first, last, n);
  /* 要是n太大了导致[first, last)中没有比n大的元素
   * 则返回数组中最后一个元素(最大的了)
   * 否则返回指向的合适的值
   */
  return pos == last ? *(last - 1) : *pos;
}

//以下正式来到hashtable的实现部分
template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey,
          class Alloc>
class hashtable {
public:
  /* 一些别名
   * 关于Key、Value、HashFcn、EqualKey前面讲解hashtable的迭代器已经提到过
   */
  typedef Key key_type;
  typedef Value value_type;
  typedef HashFcn hasher;
  typedef EqualKey key_equal;

  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是否相同的函数对象
  key_equal key_eq() const { return equals; }

private:
  /* 这三个成员都是函数对象
   * 关于它们的具体实现,都在stl_hash_fun.h中
   */
  hasher hash;
  key_equal equals;
  ExtractKey get_key;

  //节点以及以节点为分配单位的空间配置器
  typedef __hashtable_node<Value> node;
  typedef simple_alloc<node, Alloc> node_allocator;

  /* 这便是hashtable最核心的数据结构了
   * 注意它存储的元素是node*的
   * 因为每个元素都可能是一个链表的头节点
   */
  vector<node*,Alloc> buckets;
  //记录元素(节点)的个数
  size_type num_elements;

public:
  //迭代器
  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:
  /* 构造函数
   * 设置哈希函数等
   * 最后调用initialize_buckets初始化hashtable,这个函数放在下面讲
   */
  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);
  }

  /* 构造函数,默认的节点取key方法 */
  hashtable(size_type n,
            const HashFcn&    hf,
            const EqualKey&   eql)
    : hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
  {
    initialize_buckets(n);
  }

  /* 拷贝构造函数
   * 设置其哈希函数等
   * 然后调用copy_from函数,这个也放在后面分析
   */
  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;
  }
  //析构函数,调用clear()回收空间
  ~hashtable() { clear(); }
部分成员函数
  //返回节点个数
  size_type size() const { return num_elements; }
  size_type max_size() const { return size_type(-1); }
  //判断hashtable是否为空
  bool empty() const { return size() == 0; }

  void swap(hashtable& ht)
  {
    __STD::swap(hash, ht.hash);
    __STD::swap(equals, ht.equals);
    __STD::swap(get_key, ht.get_key);
    buckets.swap(ht.buckets);
    __STD::swap(num_elements, ht.num_elements);
  }

  /* 遍历,返回指向第一个不为空的元素的迭代器
   * 否则返回end()
   */
  iterator begin()
  {
    for (size_type n = 0; n < buckets.size(); ++n)
      if (buckets[n])
        return iterator(buckets[n], this);
    return end();
  }
  /* 返回指向空节点的迭代器 */
  iterator end() { return iterator(0, this); }

  const_iterator begin() const
  {
    for (size_type n = 0; n < buckets.size(); ++n)
      if (buckets[n])
        return const_iterator(buckets[n], this);
    return end();
  }

  const_iterator end() const { return const_iterator(0, this); }

  friend bool
  operator== __STL_NULL_TMPL_ARGS (const hashtable&, const hashtable&);

public:
  //返回hashtable的节点总数
  size_type bucket_count() const { return buckets.size(); }
  //返回__stl_prime_list最大的质数
  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)
      result += 1;
    return result;
  }
  //省略的部分是关于插入的一些操作,我们放在下一小节
  ......
  /* 根据key返回指向该节点的迭代器
   * 用到了bkt_num_key函数,作用是通过key得到它放在hashtable的位置
   * 首先我们根据该key得知它处于的bucket
   * 然后遍历该链表,取得该key对应的具体的节点
   * 最后构造指向该节点的迭代器返回
   */
  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);
  }


  const_iterator find(const key_type& key) const
  {
    size_type n = bkt_num_key(key);
    const node* first;
    for ( first = buckets[n];
          first && !equals(get_key(first->val), key);
          first = first->next)
      {}
    return const_iterator(first, this);
  }

  /* 返回与key相同的节点个数
   * 里面用到了equals这个函数对象
   */
  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;
  }
  //省略的是equal_range及删除操作的定义
  //关于它们的实现,我们放在下一小节分析
  ......

private:
  //next_size通过调用前面分析的__stl_next_prime函数调整n的大小
  size_type next_size(size_type n) const { return __stl_next_prime(n); }

  /* 该函数在构造函数里面调用的初始化函数
   * 首先通过next_size调整n的大小
   * 接着使用vector中的reserve函数,预先告知vector容器有n_buckets个元素(如果忘了建议翻到之前讲vector容器部分复习下)
   * 然后把所有元素都填成null
   * 设置num_elements为0
   */
  void initialize_buckets(size_type n)
  {
    const size_type n_buckets = next_size(n);
    buckets.reserve(n_buckets);
    buckets.insert(buckets.end(), n_buckets, (node*) 0);
    num_elements = 0;
  }

  /* 以下bkt_num*部分,用于对key进行哈希判断该key应该落在vector中哪个位置 */
  size_type bkt_num_key(const key_type& key) const
  {
    return bkt_num_key(key, buckets.size());
  }

  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, size_t n) const
  {
    return hash(key) % n;
  }

  size_type bkt_num(const value_type& obj, size_t n) const
  {
    return bkt_num_key(get_key(obj), n);
  }

  /* 节点配置函数
   * 功能是申请一个节点的空间,并初始化值为obj
   */
  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));
  }

  /* 节点释放函数
   * 先析构val
   * 然后释放空间
   */
  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);

};

小结

关于hashtable部分,它采用的解决冲突的方法是链接法,并且使用vector容器存储,这是为了便于扩展大小。
我们已经分析了大部分源码,剩下的就是一些操作需要继续分析,我们放在下一节分析,主要就是插入、删除、拷贝等操作。

STL _Hashtable源码分析 _Hashtable STL中并没有提供直接提供hashtable,而是通过将其封装为unordered_map和unordered_set来供外部使用,也可以说它们是由_Hashtable来实现的。 其_Hashtable的结构如下,其与传统的hash list有相同点但也存在些许不同。 同: _M_buckets为nodes指针数组,其保存了node的地址。而nodes中主要保存了2个元素,分别为next指针和value。 增删改查过程基本相似,主要分为两个步骤。首先计算hash(key),获取的bu 阅读详情

相关推荐

c++中hash_table以及std::map应用案例

代码重点是hash_table,附加std::map与其做对比,实现的是一条sql语句:select c_nationkey, c_mktsegment, count(*), max(c_acctbal) from aaa_customer_1g group by c_nationkey, c_mktsegment order by c_nationkey, c_mktsegment

C++--哈希表--开散列(哈希桶的模拟实现)--1110

手把手教你模拟开散列的哈希表

qq_68741368的博客 537

STM32F407与AD7766实战:如何用SPI+DMA实现高速ADC数据采集(附完整代码)

本文详细介绍了如何利用STM32F407的SPI接口与DMA控制器,高效稳定地驱动24位高精度ADC芯片AD7766,实现高速连续数据采集。通过对比传统中断方式的瓶颈,阐述了SPI+DMA方案如何大幅降低CPU负载,并提供了从硬件设计、CubeMX配置到核心驱动代码的完整实战指南,帮助工程师解决高速ADC数据采集中的性能难题。

weixin_29281941的博客 186

C++STL之散列表

散列表(哈希表)是普通数组概念的推广。在散列表中,不是直接把关键字作为数组的下标,而是根据关键字计算出相应的下标。 一个散列表中重要的是他的散列函数以及解决冲突的方法。 这里试着自己模仿网上STL中hash表的实现写了一个,记在这里以便以后查看。 这里散列函数选择的是除法散列法,解决冲突的方法选用的是链接法,表基于vector实现。 代码如下: //hashtable.h #ifnde...

Cold_Sun_的博客 1429

Android应用隐私合规自动化检测:Frida与Camille实战指南

在移动应用安全与隐私合规领域,动态代码插桩技术已成为核心检测手段。其原理是通过运行时注入,实时监控和修改应用行为,从而绕过传统静态分析的局限。这项技术的核心价值在于能够精准捕获应用在运行时的敏感数据调用与网络传输行为,为隐私合规审计、安全测试提供直接证据。在实际应用场景中,它尤其适用于自动化检测违规收集个人信息、追踪数据流向等任务。本文聚焦于利用Frida这一动态插桩框架,结合Camille隐私合规检测工具集,构建自动化监控环境。通过注入JavaScript代码,实现对Android敏感API(如获取设备标

weixin_34254464的博客 388

【计算机三级-数据库技术】操作题大题(第1套)

从触发器的语法中可知,【1】处应该填写的是执行触发器的表,【2】处应该填写指定在表或视图上执行哪些数据修改语句时将激活触发器的关键字。使用FOR或AFTER选顶定义的触发器为后触发器,即只有在引发触发器执行的语句中的操作都已成功执行,并且所有的约束检查也成功完成后,才执行触发器。转换成新的独立的模式,模式的属性由联系本身的属性及两个实体的键构成,主键由两端实体的键组合而成。实体的转换:在从ER图转换为关系模式时,一个实体转换成一个关系模式,实体的属性就是关系模式的属性,实体的键就是关系的主键。

放纵小开爱的博客 1101

STL学习笔记之容器--hashtable

之前对hash一直存在恐惧感,毕竟没用过……最近在一个组件里面自己实现了一个hashtable,感觉也就这么回事;回头看看书上对hashtable的分析,发现是极其的相似。不过,旧版本的C++标准里面并没有hashtable这个东西,而C++11中引入了相关的容器(std::unordered_set, std::unordered_multiset, std::unordered_map, st

wei.zhou的专栏 814

手把手教你用LcmSim+TakeDotLib打造完美LCD显示效果(附12x12汉字配置模板)

本文详细介绍了如何使用LcmSim模拟器与TakeDotLib标准字库工具优化LCD显示效果,包括字体选择、字库生成和实体设备验证。通过12x12汉字与8x16 ASCII的黄金比例配置,帮助开发者解决嵌入式开发中的显示难题,提升用户体验。附赠12x12汉字配置模板,可直接用于项目。

weixin_29205061的博客 135

SGISTL源码探究-STL中的hashtable(下)

前言在上一小节中,我们介绍了hashtable的基本概念,以及分析了在STL中,hashtable的迭代器、采用的数据结构及它的构造/析构函数,还有部分成员函数。 在本小节中,我们讲继续分析剩下的一些操作,如插入、删除、复制等。插入操作关于插入操作,有大量的重载版本,我们只分析比较关键的函数,其他的可以随便看看。/* insert_unique,插入元素(不允许重复) * 首先先判断加上新增元素

Move_now的博客 1184

MONAILabel in 3D Slicer 案例1: 在腹部CT中自动分割脾脏

MONAILabel in 3D Slicer 案例1: 在腹部CT中自动分割脾脏。

Tina姐的博客 1939

从零构建法律案例数据库:Python爬虫实战与法律文本挖掘全攻略

在当今数字化浪潮席卷全球的背景下,法律行业正经历着前所未有的变革。法律案例数据库作为法律大数据的重要组成部分,已经成为法律从业者、研究人员、法学院学生乃至企业法务部门的必备工具。然而,商业法律数据库如北大法宝、威科先行、LexisNexis等往往价格不菲,且数据访问受限。对于个人研究者或小型机构而言,构建一个定制化的法律案例数据库不仅能节省成本,更能根据特定研究需求灵活调整数据结构。

2201_76125261的博客 538

SGISTL源码阅读二十三 hashtable下

SGISTL源码阅读二十三 hashtable下 前言 之前介绍了hashtable的基本结构和它的构造方法,现在我们继续来学习它的迭代器和一些常用方法。 深入源码 hashtable的迭代器 //迭代器的声明 template &amp;amp;lt;class Value, class Key, class HashFcn, class ExtractKey, class EqualKe...

Lyn&Xx 274

硬件描述语言Verilog学习(一)

主要学习综合和仿真、模块结构和信号类型Verilog 是用代码的形式描述硬件的功能,最终在硬件电路上实现该功能。在 Verilog 描述出硬件功能后需要使用综合器对 Verilog 代码进行解释并将代码转化成实际的电路来表示,最终产生实际的电路,也被称为网表。这种将 Verilog 代码转成网表的工具就是综合器。 上图左上角是一段 Verilog 代码,该代码实现了一个加法器的功能。在经过综合器解释后该代码被转化成一个加法器电路。QUARTUS、ISE 和 VIVADO 等 FPGA 开发工具都是综合器,

安静到无声 1499

STL源码剖析hashtable

在使用hashtable的时候,不能直接调用<stl_hashtable.h>,应该含乳有用到hashtable的容器头文件,例如:<hash_set.h>和<hash_map.h>。 #include<hash_set> #include<hash_map> hash function只能处理int,short,long,char...

ThorKing01的博客 469

Android14 SystemUI如何实现下拉菜单栏高斯模糊背景效果(类似于毛玻璃效果)

我们都知道,从Android12起,下拉菜单栏控制中心都是黑色的,看起来非常不美观,而且国内的手机系统UI都做的比较美观,大部分的下拉菜单UI背景都是实现了高斯模糊效果,所以客户也会要求实现类似于这样的效果。在以前的Android项目中,实现下拉菜单栏的模糊背景就是通过截图的方式进行替换背景,而这种方式下拉菜单背景极易替换不了,就算是在各个地方都适配,还是有概率在复杂场景或者一些测试手法下出现背景是黑的情况,并且这种方式还比较影响性能,毕竟每次都要去截图一次,多做了额外的事。

热衷于Android Framework和系统APP的修改经验分享 1793

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

SGISTL源码阅读二十二 hashtable上

SGISTL源码阅读二十二 hashtable上 前言 hashtable的部分基本知识 hashtable,哈希表,也可称为散列表。 哈希表用于以常数平均时间执行插入、删除和查找操作,每个关键字按照某种规定被映射到从0-TableSize-1这个范围中的某个数,并且被放到适当的单元中。 时间复杂度可以达到常数级别听起来让人觉得很兴奋,但是它也是需要付出代价的,哈希表的一个最重要的问题就是解决冲突...

Lyn&Xx 285

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SGISTL源码阅读二十五 hash_map 关联式容器

SGISTL源码阅读二十五 hash_map 关联式容器 前言 前面我们已经分析了底层容器为红黑树的map和hashtable,而接下来我们要分析的hash_map,其底层容器为hashtable。 深入源码 hash_map的定义部分 #ifndef __STL_LIMITED_DEFAULT_TEMPLATES template &amp;lt;class Key, class T, class H...

Lyn&Xx 232

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

SGI STL中hashtable的erase_bucket成员函数中的一个错误

以上代码摘自SGI STL的hashtable中的源码,erase在调用erase_bucket时的语义删除指定bucket桶子内[first, last)范围的元素(0表示null,删除到结尾),但是源码确是当first不为buckets[n] 第一个节点时将first后的所有元素删除。

weixin_51829359的博客 188

《STL源码剖析》---stl_hash_map.h阅读笔记

SGI STL中的map底层以红黑树实现,hash_map以hash table实现。 hash_map不允许插入重新键值,hash_multimap允许插入重复键值。这两者的关系就像map和multimap的关系。底层的hash table提供的大部分的操作,hash_map(hash_multimap)大部分都是直接调用hash table的函数。

KangRoger的专栏 2344

从零实现STL哈希容器:unordered_map/unordered_set封装详解

从0开始,实现STL哈希容器:unordered_map/unordered_set封装!!!

2301_80029060的博客 920

SGISTL源码探究-关联式容器:hash_set

前言在上小节中,我们已经分析了hashtable的实现,它只是hash_set和hash_map的预备知识。在本小节中,我们将进入到关联式容器hash_set的源码中,一窥究竟。hash_set的实现由于hash_set并没有自己定义的迭代器,所以接下来我们先对它的定义部分以及数据结构进行分析,然后是它的构造函数以及常用操作等。定义部分及数据结构#ifndef __STL_LIMITED_DEFAU

Move_now的博客 876

STL源码剖析——hash_map解析

目录 hash table 的节点定义: hashtable 的迭代器: hashtable 的数据结构 rehash和插入操作 复制与整体删除 C++实现的hashmap简易类 STL的的hash_set,hash_map都是基于hash_table实现的,SGI STL 中哈希表采用链接法解决冲突。结构中维护了一个 vector,vector 中每一个元素称为一个桶(bucket...

天行健,君子以自强不息 933

SGISTL源码阅读二十四 hash_set 关联式容器

SGISTL源码阅读二十四 hash_set 关联式容器 前言 前面我们已经分析了set和hashtable,set的底层容器是红黑树,而接下来我们要分析的hash_set,是底层为hashtable的集合。 深入源码 hash_set的定义部分 #ifndef __STL_LIMITED_DEFAULT_TEMPLATES template &lt;class Value, class Has...

Lyn&Xx 207

STL源码剖析 - 第5章 关联式容器 - hash_set

5.8 hash_set    hash_set以hashtable为底层机制。和set类似,hast_set的键值就是实值,实值就是键值,但是不同点是hash_set容器中的元素是没有排序的,因为hash table没有提供排序功能。hash_set的使用方式与set完全相同。本文源码出自SGISTL的文件。 hash_set容器源码剖析 #ifndef __SGI_STL_INTERNA

灯火阑珊处 532

深入剖析:C++ 手写实现 unordered_map 与 unordered_set 全流程指南

在现代 C++ 开发中,和是开发者高频使用的数据结构。它们底层使用哈希表(hash table)实现,提供O(1)平均复杂度的插入和查找,性能极高。但作为工程师,不能只做容器的使用者,更要理解其背后的实现机制。本文将从底层原理、SGI STL 源码出发,手动模拟并完整实现一套泛型、可扩展、支持迭代器的与容器。size_t _n;// 元素个数public:public:public:迭代器功能:支持it!= end();++it)封装当前节点和哈希表指针。

2406_83947720的博客 1160

【STL学习】自己动手C++编程实现hash table(散列表)

SGI STL中散列表采用链接法解决冲突。结构中维护了一个vector,vector中每一个元素称为一个桶(bucket),它包含的是一个链表的第一个节点。 下面代码展示了自己编程实现的hash table,C++模板类封装。 如有错误,还请包涵和指正(E-Mail:xiajunhust@gmail.com)! HashTable.h: //《STL源码剖析》5.7

计算机专业同学的充电站。 6748
上一篇: SGISTL源码探究-关联式容器:multimap
下一篇: SGISTL源码探究-STL中的hashtable(下)
UKey_
博客等级 码龄10年 258粉丝 186原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值