整数集合 实现原理
整数集合(IntSet)是 Redis 中用于存储整数的数据结构。它具有以下特点:
- 节省内存:整数集合采用了紧凑的存储方式,能够有效地节省内存空间。它会根据存储的整数范围自动选择合适的编码方式,例如,如果存储的整数都在较小的范围内,就会使用更紧凑的编码方式。
- 支持快速查找:整数集合在查找元素时具有较高的效率,可以快速判断一个整数是否存在于集合中。
- 自动升级:当需要添加的整数超出了当前编码方式所能表示的范围时,整数集合会自动升级编码方式,以容纳更大范围的整数。
- 不允许重复元素:整数集合中不允许存在重复的元素。
源码对应文件:
intset.c
intset.h
源码及原理
数据结构
typedef struct intset {
uint32_t encoding; // 编码
uint32_t length; // 长度
int8_t contents[]; // 内容
} intset;
uint32_t encoding
:表示整数集合的编码方式。不同的编码方式决定了contents
数组中存储整数的方式和范围。uint32_t length
:表示整数集合中元素的数量。int8_t contents[]
:这是一个可变长度的数组,用于存储整数集合的实际内容。
升级操作
源码:
/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) { // 升级intset到更大的编码并插入给定的整数
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
}
当把一个元素插入到整数集合时,如果这个元素比所有元素都长,那么就要进行升级,升级后每个元素大小为新的元素大小。
升级不会分配新数组,就在原来数组进行扩展,需要先扩容原来的数组。
使用整数升级机制的好处就是节省空间。
比如原来都是int16
大小,那么就用不到int64
,直到有int64
来
不支持降级
不支持降级操作,一旦对数组进行了升级,就会一直保持升级后的状态