redis设计与实现——整数集合
整数集合intset是集合键的底层实现之一,当一个集合只包含整数元素并且元素数量不多时,redis就会用intset作为集合键的底层实现。
整数集合的实现
intset的数据结构在intset.h/c的表示方式如下:
1 | typedef struct intset { |
contents保存的就是intset中各个元素,虽然其声明为int8_t,但实际上其保存的类型取决于encoding的值。
encoding的可能选项有三种:
1 |
另外,encoding的类型是由contents中最大的一个数决定的。contents数组则按小到大保存着所有元素。
创建空的intset时默认为int16:
1 | /* Create an empty intset. */ |
由于contents是一段连续的内存,并存储超过了一个字节,元素也是按照大小排序,因此需要考虑系统的大小端问题。redis都是按照小端来使用,在/src/endianconv.h中有一段相关的宏定义:
1 | /* variants of the function doing the actual conversion only if the target |
升级
每当redis要添加一个新元素到整数集合时,并且新元素的类型比当前整数集合的encoding要更长时,就需要先进行升级。
因此在这种情况下,添加一个新元素的步骤就是:升级、查找和插入。
首先要判断插入元素的长度:
1 | static uint8_t _intsetValueEncoding(int64_t v) { |
以下就是新元素插入的主题函数过程
1 | intset *intsetAdd(intset *is, int64_t value, uint8_t *success) { |
由上可见,在不进行升级的情况,需要先找到对应的pos,即intset中小于value的最大元素,通过二分法查找:
1 | static int64_t _intsetGet(intset *is, int pos) { |
前面的判断语句,在pos小于当前长度的时候,需要将pos后面的元素都往后移动一个位置。
1 | static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) { |
至于具体的升级操作,则由intsetUpgradeAndAdd完成,包含了encoding升级和插入元素。
1 | static intset *intsetUpgradeAndAdd(intset *is, int64_t value) { |
总结
- 整数集合是集合键的底层实现之一;
- redis能够根据新加元素的类型,改变整个数组的类型;
- 整数集合只支持升级,不支持降级操作;