redis源码阅读之intset

15870阅读 0评论2019-02-12 stolennnxb
分类:NOSQL

今天来讲讲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是否过大的话,就会造成不必要的消耗,还不如让其多占用点内存了~~~
上一篇:redis源码阅读之zipmap
下一篇:redis源码阅读之zskiplist