• Redis数据结构:字典(hash表)


    使用场景:

    # set person name "tom"

    # set person name "jerry"

    1. 字典结构:

    哈希表数据结构
    
    typedef struct dictht {
        //哈希表数组,存的是哈希值
        dictEntrry **table;
        
        //哈希表大小(table的大小)
        unsigned long size;
    
        //哈希表大小掩码,用于计算索引值,总是等于size-1
        unsigned long sizemask;
    
        //该哈希表已有节点的数量
        unsigned long used;
    
    } dictht;
    哈希表节点数据结构
    
    typedef struct dictEntry {
        //
        void *key;
    
        //
        union {
    //值可以是一个指针
    void *val; //也可以是一个uint64_t整数 uint64_tu64; //也可以是int64_t整数 int64_ts64; } v; //指向下个哈希表节点,形成链表,用来使用拉链法解决哈希冲突 struct dictEntry *next; } dictEntry;
    字典数据结构
    
    typedef struct dict {
        //类型特定函数(保存了一些操作特定类型键值对函数)
        dictType *type;
        
        //私有数据(保存了传给特定函数的一些参数)
        void *privdata;
    
        //哈希表(一般使用ht[0]哈希表,ht[1]是在rehash时使用)
        dictht ht[2];
    
        //rehash 索引(记录了rehash的进度,不rehash时,值为-1)
        int trehashidx;
    
    } dict;

    2. 哈希算法:

    当添加一个新键值时,会先根据键计算出哈希值和索引值,然后将新键值节点放到指定索引上(dictEntry*[index])。

    计算哈希值:hash = dict -> type -> hashFunction(key);

    计算索引值:index = hash & dict -> ht[x].sizemask;

    3. 解决哈希冲突:

    当键冲突时,使用next指针将新键连接起来。注意:为了速度考虑,程序会将新节点添加到链表的表头位置。

    4. rehash:

    随着数据的增多和减少,为了让哈希表的负载因子维持在一个合理的范围,需要对哈希表进行扩展和收缩。

    负载因子:load_factor = ht[0].used / ht[0].size

    rehash扩展触发条件:

    1). 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且负载因子大于等于1。

    2). 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且负载因子大于等于0.5。

    rehash收缩触发条件:负载因子小于0.1。

    步骤如下:

    1). 在没有rehash时,字典只使用ht[0]。在rehash时,程序会对ht[1]分配空间。

    扩展:ht[1]的大小为第一个大于等于ht[0].used * 2的2^n (2的n次方幂);

    收缩:ht[1]的大小为第一个大于等于ht[0].used的2^n;

    2).  将ht[0]的所有数据全部rehash到ht[1]上面,rehash指的是重新计算键的哈希值和索引值,然后放到ht[1]的指定位置上。

    3). 当ht[0]上的数据全部迁移到ht[1]后,释放ht[0],将ht[1]置为ht[0],并在ht[1]上新建一个空的哈希表,为下一次rehash做准备。

    5.  渐进式rehash(rehash的优化,触发条件由负载因子决定,如上)

    当哈希表的键值对数量巨大时,一次rehash会非常耗时,为了避免对服务器性能造成影响,服务器采用分批次、渐进式的将ht[0]里面的键值对慢慢的rehash到ht[1]。

    步骤:

    1)为ht[1]分配空间,字典同时持有ht[0]和ht[1]。

    2)字典中设置一个计数器rehashidx,当值为0时,表示rehash开始。

    3)在rehash期间,每次增删改查时,程序还会顺带将ht[0]在rehashidx索引上的所有键值对rehash到ht[1],当rehash完成后,rehashidx增一。(这里是将table上对应的某条索引值指向的数据节点链表迁移到ht[1]中,th[0].table[rehashidx]数据就空了)

    4)随着操作的不断执行,ht[0]上的所有键值对都会被rehash到ht[1]上,这时rehashidx为-1,表示rehash完成。

    注意:

    1)在rehash执行期间,新数据会保存到ht[1]里面,而ht[0]不进行添加操作,这样,ht[0]里的数据会逐渐迁移到ht[1],直到ht[0]清空。

    2)在rehash执行期间,查找某个键时,会先从ht[0]中查找,如果没找到就去ht[1]里面查找。

  • 相关阅读:
    Java IO编程中的几个概念
    java强转与继承关系的加深理解:object[]的数组无法强转为String[]的数组
    java反射机制获取对象中父类属性对象
    intealij idea中报错:Error during artifact deployment. See server log for details
    自定义数据属性
    字符集属性
    HTMLDocument的变化
    动态添加对象子对象,防止命名冲突
    焦点管理
    HTML5与相关类的扩充
  • 原文地址:https://www.cnblogs.com/wwzyy/p/10615939.html
Copyright © 2020-2023  润新知