文章目录
1、整数集合介绍
整数集合适用范围
整数集合(inset)是集合键的底层实现之一。当一个集合只包含整数数值元素并且这个结合的个数不是很多的时候,Redis就会使用整数集合来作为集合键的底层实现。
整数集合的特点
1、整数集合是有序的
2、整数集合是不重复的
3、整数集合只能保存整数且必须为正数或者0
2、整数集合的实现
整数集合添加元素的的过程介绍
3、整数集合添加节点实现细节
3.1、如何判断新加入的节点的encoding值是否超值?
为什么会有这种机制
inset为了最大化的减少内存空间,默认inset申请后的每一个encoding的为INTSET_ENC_INT16,只要后面的数据不超过16位,则一直为每一个数据申请INTSET_ENC_INT16的空间进行填充。但是如果之后有一个数据超过了16位,例如达到了32位,则需要进行扩容,扩容后所有的数据申请的位数都为32位。并且如果之后这个32位的数据删除了,inset数组也不会自动进行减容,即不会退回到16位。
传入值的大小种类一共有三种,分别是
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
inset的结构体为
typedef struct intset {
//指的是INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64中的一种
uint32_t encoding;
//整数集合的长度
uint32_t length;
//整数集合的内容
int8_t contents[];
} intset;
static uint8_t _intsetValueEncoding(int64_t v) {
if (v < INT32_MIN || v > INT32_MAX)
return INTSET_ENC_INT64;
else if (v < INT16_MIN || v > INT16_MAX)
return INTSET_ENC_INT32;
return INTSET_ENC_INT16;
}
在 _intsetValueEncoding函数中通过判断新节点的值来得到他的encoding的值,和inset集合中保存的encoding进行判断可以得到
3.2、inset集合如何进行扩大数据空间?
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
//获取encoding
uint8_t curenc = is->encoding;
//获取最新值的encoding
uint8_t newenc = _intsetValueEncoding(value);
int length = is->length;
int prepend = value < 0 ? 1 : 0;
//因为已经进入intsetUpgradeAndAdd函数,所以新的encoding必定大于原来的encoding
//因此这里进行更新
is->encoding = newenc;
//对内存空间进行扩容
is = intsetResize(is,is->length+1);
//在扩容过程中申请了length+1的空间
//如果为正数,一定是加在最后面,所以在最后面留了一个空间
//如果为负数,一定为0,加在最前面,根据prepend,最后面的空间被占据,最前面溜了一个空间
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
//填充新加入的值
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,is->length,value);
//更新长度
is->length++;
return is;
}
总的来说,就是先申请足够大的内存,然后依次从后向前填充数据。根据正数还是负数,放入到适合的位置。
3.3、如何查找新加入的值是否存在,存在的话位置在哪?
查找的函数是intsetSearch
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = 0, max = is->length-1, mid = -1;
int64_t cur = -1;
/* The value can never be found when the set is empty */
if (is->length == 0) {
if (pos) *pos = 0;
return 0;
} else {
//判断新的值是否大于inset中的最大值
if (value > _intsetGet(is,is->length-1)) {
if (pos) *pos = is->length;
return 0;
//判断新加入的值是否小于inset中的最小值
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
}
//根据二分法来进行查找
while(max >= min) {
mid = (min+max)/2;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+1;
} else if (value < cur) {
max = mid-1;
} else {
break;
}
}
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
if (pos) *pos = min;
return 0;
}
}
3.4、如何移动inset中数据位置
因为如果需要插入的位置是中间的,那么需要将其后面的数据都后移一位
static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
void *src, *dst;
uint32_t bytes = is->length-from;
if (is->encoding == INTSET_ENC_INT64) {
src = (int64_t*)is->contents+from;
dst = (int64_t*)is->contents+to;
bytes *= sizeof(int64_t);
} else if (is->encoding == INTSET_ENC_INT32) {
src = (int32_t*)is->contents+from;
dst = (int32_t*)is->contents+to;
bytes *= sizeof(int32_t);
} else {
src = (int16_t*)is->contents+from;
dst = (int16_t*)is->contents+to;
bytes *= sizeof(int16_t);
}
memmove(dst,src,bytes);
}
利用memmove函数将其后面的数据都后移一位
3.5、添加数据的函数实现
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 > 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,is->length+1);
if (pos < is->length) intsetMoveTail(is,pos,pos+1);
}
_intsetSet(is,pos,value);
is->length++;
return is;
}
利用以上步骤,就可以成功的将一个数据添加到inset中。