今天来讲讲redis当中set的一种实现形式intset,顾名思义,其应用场景只是在集合当中只包含整数值并且元素数量不多时,set才会采用的一种实现方式。
其存储结构如下所示:
-
typedef struct intset {
-
uint32_t encoding; // 编码规则,具体见下面三个宏定义
-
uint32_t length; // 所存元素个数
-
int8_t contents[]; // 元素存储位置
-
} intset;
-
#define INTSET_ENC_INT16 (sizeof(int16_t))
-
#define INTSET_ENC_INT32 (sizeof(int32_t))
-
#define INTSET_ENC_INT64 (sizeof(int64_t))
其中三个宏定义表示的是encoding字段的可能取值,分别对应intset中的contents数组的三种不同的解析方式(16位整数,32位整数,64位整数)。
这里需要注意的是由于不同的机器本地的endian可能不同,redis使用了endianconv.h文件中的函数对这方面进行了统一。在contents中存放的实际数字时按照从小到大的顺序排列好的,并且没有重复项。
整个intset里面比较好玩的就是插入元素了。由于有三种不同的编码方式,当插入的新元素所占的空间较大,大到目前intset自身的编码无法承载这一数据的时候,intset就会根据新插入的值得大小,将原有的inset的encoding变为需要的,其具体步骤如下:
-
根据插入的元素类型,适当扩展intset所占的空间
-
在维持低层数组存储数据不变的基础上,按照从后到前的顺序(避免覆盖)拷贝到新的位置上
-
将新添加的元素插入到intset当中
其中插入代码如下所示:
-
/* Insert an integer in the intset */
-
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
-
uint8_t valenc = _intsetValueEncoding(value);
-
uint32_t pos;
-
if (success) *success = 1;
-
-
/* Upgrade encoding if necessary. If we need to upgrade, we know that
-
* this value should be either appended (if > 0) or prepended (if < 0),
-
* because it lies outside the range of existing values. */
-
if (valenc > intrev32ifbe(is->encoding)) {
-
/* This always succeeds, so we don't need to curry *success. */
-
return intsetUpgradeAndAdd(is,value);
-
} else {
-
/* Abort if the value is already present in the set.
-
* This call will populate "pos" with the right position to insert
-
* the value when it cannot be found. */
-
if (intsetSearch(is,value,&pos)) {
-
if (success) *success = 0;
-
return is;
-
}
-
-
is = intsetResize(is,intrev32ifbe(is->length)+1);
-
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
-
}
-
-
_intsetSet(is,pos,value);
-
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
-
return is;
-
}
在这个函数当中,首先判断了插入值的编码,若应该分配较大的空间,直接调用update函数
-
/* Upgrades the intset to a larger encoding and inserts the given integer. */
-
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
-
uint8_t curenc = intrev32ifbe(is->encoding);
-
uint8_t newenc = _intsetValueEncoding(value);
-
int length = intrev32ifbe(is->length);
-
int prepend = value < 0 ? 1 : 0;
-
-
/* First set new encoding and resize */
-
is->encoding = intrev32ifbe(newenc);
-
is = intsetResize(is,intrev32ifbe(is->length)+1);
-
-
/* Upgrade back-to-front so we don't overwrite values.
-
* Note that the "prepend" variable is used to make sure we have an empty
-
* space at either the beginning or the end of the intset.
-
* 通俗的将就是对新插入的元素,我们应该插在头部还是插在尾部 */
-
while(length--)
-
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
-
-
/* Set the value at the beginning or the end. */
-
if (prepend)
-
_intsetSet(is,0,value);
-
else
-
_intsetSet(is,intrev32ifbe(is->length),value);
-
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
-
return is;
-
}
如果当前intset当中已经有该元素的话,直接返回。若没有,需要找到应该插入的位置,移动其后面的元素,再将该值插入到应该插入的位置。
删除操作的话,逻辑就是能否找到,若找到的话,就直接将后面的元素移动过来,直接覆盖即可
-
/* Delete integer from intset */
-
intset *intsetRemove(intset *is, int64_t value, int *success) {
-
uint8_t valenc = _intsetValueEncoding(value);
-
uint32_t pos;
-
if (success) *success = 0;
-
-
if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
-
uint32_t len = intrev32ifbe(is->length);
-
-
/* We know we can delete */
-
if (success) *success = 1;
-
-
/* Overwrite value with tail and update length */
-
if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
-
is = intsetResize(is,len-1);
-
is->length = intrev32ifbe(len-1);
-
}
-
return is;
-
}
这里面需要提醒诸位的是:
删除元素的时候没有降级操作!!!个人猜测的原因为:如果删除一个元素就要从头到尾检查当前的encoding是否过大的话,就会造成不必要的消耗,还不如让其多占用点内存了~~~
阅读(23813) | 评论(0) | 转发(0) |