• 社区 —频道 春城壹网 七彩云南 一网天下 2019-03-17
  • 陈坤9年之后重回小荧屏 2019-03-17
  • 医院建在“云端”上(聚焦·互联网医院新观察(上)) 2019-03-09
  • 山东十一选五专家杀号 Chinaunix首页 | 论坛 | 山东十一选五专家杀号
    • 博客访问: 209365
    • 博文数量: 59
    • 博客积分: 0
    • 博客等级: 民兵
    • 技术积分: 713
    • 用 户 组: 普通用户
    • 注册时间: 2016-12-21 22:26
    个人简介

    90后空巢老码农

    文章分类

    全部博文(59)

    文章存档

    2019年(11)

    2018年(47)

    2017年(1)

    我的朋友

    分类: NOSQL

    2019-02-12 18:09:02

    今天来讲讲redis当中set的一种实现形式intset,顾名思义,其应用场景只是在集合当中只包含整数值并且元素数量不多时,set才会采用的一种实现方式。
    其存储结构如下所示:

    点击(此处)折叠或打开

    1. typedef struct intset {
    2.     uint32_t encoding; // 编码规则,具体见下面三个宏定义
    3.     uint32_t length; // 所存元素个数
    4.     int8_t contents[]; // 元素存储位置
    5. } intset;
    6. #define INTSET_ENC_INT16 (sizeof(int16_t))
    7. #define INTSET_ENC_INT32 (sizeof(int32_t))
    8. #define INTSET_ENC_INT64 (sizeof(int64_t))
    其中三个宏定义表示的是encoding字段的可能取值,分别对应intset中的contents数组的三种不同的解析方式(16位整数,32位整数,64位整数)。这里需要注意的是由于不同的机器本地的endian可能不同,redis使用了endianconv.h文件中的函数对这方面进行了统一。在contents中存放的实际数字时按照从小到大的顺序排列好的,并且没有重复项。
    整个intset里面比较好玩的就是插入元素了。由于有三种不同的编码方式,当插入的新元素所占的空间较大,大到目前intset自身的编码无法承载这一数据的时候,intset就会根据新插入的值得大小,将原有的inset的encoding变为需要的,其具体步骤如下:
    1. 根据插入的元素类型,适当扩展intset所占的空间
    2. 在维持低层数组存储数据不变的基础上,按照从后到前的顺序(避免覆盖)拷贝到新的位置上
    3. 将新添加的元素插入到intset当中
    其中插入代码如下所示:

    点击(此处)折叠或打开

    1. /* Insert an integer in the intset */
    2. intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    3.     uint8_t valenc = _intsetValueEncoding(value);
    4.     uint32_t pos;
    5.     if (success) *success = 1;

    6.     /* Upgrade encoding if necessary. If we need to upgrade, we know that
    7.      * this value should be either appended (if > 0) or prepended (if < 0),
    8.      * because it lies outside the range of existing values. */
    9.     if (valenc > intrev32ifbe(is->encoding)) {
    10.         /* This always succeeds, so we don't need to curry *success. */
    11.         return intsetUpgradeAndAdd(is,value);
    12.     } else {
    13.         /* Abort if the value is already present in the set.
    14.          * This call will populate "pos" with the right position to insert
    15.          * the value when it cannot be found. */
    16.         if (intsetSearch(is,value,&pos)) {
    17.             if (success) *success = 0;
    18.             return is;
    19.         }

    20.         is = intsetResize(is,intrev32ifbe(is->length)+1);
    21.         if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    22.     }

    23.     _intsetSet(is,pos,value);
    24.     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    25.     return is;
    26. }
    在这个函数当中,首先判断了插入值的编码,若应该分配较大的空间,直接调用update函数

    点击(此处)折叠或打开

    1. /* Upgrades the intset to a larger encoding and inserts the given integer. */
    2. static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    3.     uint8_t curenc = intrev32ifbe(is->encoding);
    4.     uint8_t newenc = _intsetValueEncoding(value);
    5.     int length = intrev32ifbe(is->length);
    6.     int prepend = value < 0 ? 1 : 0;

    7.     /* First set new encoding and resize */
    8.     is->encoding = intrev32ifbe(newenc);
    9.     is = intsetResize(is,intrev32ifbe(is->length)+1);

    10.     /* Upgrade back-to-front so we don't overwrite values.
    11.      * Note that the "prepend" variable is used to make sure we have an empty
    12.      * space at either the beginning or the end of the intset. 
    13.      * 通俗的将就是对新插入的元素,我们应该插在头部还是插在尾部 */
    14.     while(length--)
    15.         _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    16.     /* Set the value at the beginning or the end. */
    17.     if (prepend)
    18.         _intsetSet(is,0,value);
    19.     else
    20.         _intsetSet(is,intrev32ifbe(is->length),value);
    21.     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    22.     return is;
    23. }
    如果当前intset当中已经有该元素的话,直接返回。若没有,需要找到应该插入的位置,移动其后面的元素,再将该值插入到应该插入的位置。
    删除操作的话,逻辑就是能否找到,若找到的话,就直接将后面的元素移动过来,直接覆盖即可

    点击(此处)折叠或打开

    1. /* Delete integer from intset */
    2. intset *intsetRemove(intset *is, int64_t value, int *success) {
    3.     uint8_t valenc = _intsetValueEncoding(value);
    4.     uint32_t pos;
    5.     if (success) *success = 0;

    6.     if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
    7.         uint32_t len = intrev32ifbe(is->length);

    8.         /* We know we can delete */
    9.         if (success) *success = 1;

    10.         /* Overwrite value with tail and update length */
    11.         if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
    12.         is = intsetResize(is,len-1);
    13.         is->length = intrev32ifbe(len-1);
    14.     }
    15.     return is;
    16. }
    这里面需要提醒诸位的是:删除元素的时候没有降级操作?。?!个人猜测的原因为:如果删除一个元素就要从头到尾检查当前的encoding是否过大的话,就会造成不必要的消耗,还不如让其多占用点内存了~~~
    阅读(5388) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~
    评论热议
    请登录后评论。

    登录 注册

  • 社区 —频道 春城壹网 七彩云南 一网天下 2019-03-17
  • 陈坤9年之后重回小荧屏 2019-03-17
  • 医院建在“云端”上(聚焦·互联网医院新观察(上)) 2019-03-09