整数集合的实现
整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_t
、 int32_t
或者 int64_t
的整数值, 并且保证集合中不会出现重复元素,同时底层数组中的元素按从小到大的顺序排列。
每个 intset.h/intset
结构表示一个整数集合:
typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; } intset;
以下展示了一个整数集合的示例:
encoding
属性的值为INTSET_ENC_INT16
,表示集合保存的都是int16_t
类型的整数值。length
属性的值为5
, 表示整数集合包含五个元素。contents
数组按从小到大的顺序保存着集合中的五个元素。
升级
升级分为以下几步进行:
- 根据新元素的类型, 扩展底层数组的空间大小, 并为新元素分配空间。
- 将底层数组的元素都转换成与新元素相同的类型, 并将类型转换后的元素放置到正确的位上。
- 添加新元素到底层数组。
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) { // 当前的编码方式 uint8_t curenc = intrev32ifbe(is->encoding); // 新值所需的编码方式 uint8_t newenc = _intsetValueEncoding(value); // 当前集合的元素数量 int length = intrev32ifbe(is->length); // 进行升级时,value 要么大于集合中的所有元素,要么小于集合中的所有元素
int prepend = value < 0 ? 1 : 0; // 更新集合的编码方式 is->encoding = intrev32ifbe(newenc); // 根据新编码对集合(的底层数组)进行空间调整 is = intsetResize(is,intrev32ifbe(is->length)+1); //原先元素类型转换 while(length--) _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); // 设置新值,根据 prepend 的值来决定是添加到数组头还是数组尾 if (prepend) _intsetSet(is,0,value); else _intsetSet(is,intrev32ifbe(is->length),value); // 更新整数集合的元素数量 is->length = intrev32ifbe(intrev32ifbe(is->length)+1); return is; }
对于是否升级在添加元素时进行判断:
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) { ....... // 如果 value 的编码比整数集合现在的编码要大,进行升级 if (valenc > intrev32ifbe(is->encoding)) {
return intsetUpgradeAndAdd(is,value);
} else { // 运行到这里,表示整数集合现有的编码方式适用于 value // 正常插入元素操作 }
....... }
需要注意的是,整数集合不支持降级操作,一旦升级,编码会一直保持升级后的状态。
整数集合的编码有以下几种:
/* * intset 的编码方式 */ #define INTSET_ENC_INT16 (sizeof(int16_t)) #define INTSET_ENC_INT32 (sizeof(int32_t)) #define INTSET_ENC_INT64 (sizeof(int64_t))