redis- 整数集合
整数集合(intset)是集合键的底层实现之一。当一个集合只包括整数元素并且元素个数不多时,Redis将会使用整数集合来存储。
看下面例子,当集合s1只存储整数1、2、4时,底层采用intset来存储;往集合s1中添加字符串”hello”后,底层存储变为哈希表。
redis的集合数据结构(set)其实是利用哈希表实现的,哈希表的键就是集合的元素,哈希表的值都是NULL。
127.0.0.1:6379> sadd s1 1 2 4
(integer) 3
127.0.0.1:6379> object encoding s1
"intset"
127.0.0.1:6379> sadd s1 "hello"
(integer) 1
127.0.0.1:6379> object encoding s1
"hashtable"
以下片断是t_set.c
的部分代码,从注释中可以看到,当值是一个整数值时返回intset,否则返回一个hashtable。
/* Factory method to return a set that *can* hold "value". When the object has
* an integer-encodable value, an intset will be returned. Otherwise a regular
* hash table. */
robj *setTypeCreate(robj *value) {
if (isObjectRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject();
return createSetObject();
}
整数集合的实现
redis整数集合可以存储int16_t、int32_t或者int64_t类型的整数值,并且保证集合中不存在重复元素。
redis的整数集合定义如下:
typedef struct intset {
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
} intset;
- encoding:整数集合保存的数据类型,encoding属性取值范围包括:
- INTSET_ENC_INT16,此时contents数组元素值的范围为[-2^15^, 2^15^-1]
- INTSET_ENC_INT32,此时contents数组元素值的范围为[-2^31^, 2^31^-1]
- INTSET_ENC_INT64,此时contents数组元素值的范围为[-2^63^, 2^63^-1]
- length:整数集合包含的元素数量,也就是contents数组的大小
- contents:实际存储元素的数组,虽然contents数组的类型声明为int8_t,但是实际使用是根据encoding来决定数组存储的整数类型的。另外,contents中的元素是根据元素大小从小到大排列的,且contents中不存在任何重复项。
图1
图1是一个包括6个元素的整数集合,其中元素是int16_t类型的。
升级
对于图1的例子,如果此时往该整数集合插入一个int32_t类型的值,比如插入2010483000,显然原来的int16_t类型存储不了该值,此时需要对整数集合进行升级。
升级步骤为:
- 根据新元素的类型,扩展底层存储空间的大小,并为新添加的元素预留存储空间。
- 调整原有数据的数据类型为新的数据类型,预留每个元素的空间。
- 将新添加的元素放到整数集合的正确位置。
例如对于图1,此时要插入2010483000,需要将原有的数据类型升级为int32_t
- 首先计算需要的空间大小。元素个数有7个,每个大小为32位,总共有7x32=224位,空间分配如图2。
图2
从图2可以看出,新分配的空间位数为96-223位。
元素198排在第6位,所以它的最终位置为160-191位;
元素89排在第5位,所以它的最终位置为128-159位;
元素45排在第4位,所以它的最终位置为96-127位;
元素23排第3位,所以它的最终位置为64-95位;
元素-456排在第2位,所以它的最终位置为32-63位;
元素-1234排在第1位,所以它的最终位置为0-31位;
最终如图3所示
图3
新插入的元素放到第7位的空间,最终如图4所示。
图4
最后程序将encoding属性值改为INTSET_ENC_INT32,将length属性值改为7。
注意:如果新添加的元素引起了整数集合的升级,那么这个元素要么是比原来所有的元素都大,要么比原来所有的元素都小(负数)。因此新添加的元素要么位于数组contents索引0处,要么位于数组末尾。
为何要升级?
redis这样设计可以节省内存的使用,只有需要的时候才会去重新分配更多的内存,提高了内存的使用效率。
降级
redis不支持降级,当整数类型升级后不可以降级。比如整数集合只有一个类型为int32_t的元素,其他的元素用int16_t类型就可以存储,此时将该元素删除后,整数集合并不会降级为int16_t,而还是维持int32_t类型的。
注意点
- 整数集合底层是用数组存储的,所以添加和移除的时间复杂度为O(n)
- 整数集合底层存储是有序的,所以查找操作可以用二分查找,时间复杂度为O(log^N^)
参考:
- Redis设计与实现. 黄健宏著.