IntSet
简介
Redis 的 intset
(整数集合)是一种用于存储整数的有序集合数据结构。它在 Redis 的实现中被广泛用于存储有序的整数集合。
具体实现
基本结构
结构定义如下:
1 2 3 4 5
| typedef struct intset { uint32_t encoding; uint32_t length; int8_t contents[]; } intset;
|
Redis会将intset中所有的整数按照升序依次保存在contents数组中,结构如图:

由于保存了contens数组的头地址,且数组是一片连续内存,可以通过地址+偏移的方式快速查到指定下标的元素。
空间效率
intset
通过动态调整整数的存储类型,根据存储的整数范围自动选择最合适的编码方式,从而节省内存。encoding包含三种模式,表示存储的整数大小不同:
1 2 3
| #define INTSET_ENC_INT16 (sizeof(int16_t)) #define INTSET_ENC_INT32 (sizeof(int32_t)) #define INTSET_ENC_INT64 (sizeof(int64_t))
|
自动升级
当插入一个超出当前编码范围的整数时,intset
会自动升级到更大的编码类型(例如从 INT16
升级到 INT32
),以支持更大的整数。
例如现在有一个intset,元素为{5,10,20},采用的编码是INTSET_ENC_INT16,则每个整数占2字节:

向该其中添加一个数字:50000,这个数字超出了int16_t的范围,intset会自动升级编码方式到合适的大小。流程如下:
- 升级编码为INTSET_ENC_INT32, 每个整数占4字节,并按照新的编码方式及元素个数扩容数组;
- 倒序依次将数组中的元素拷贝到扩容后的正确位置;
- 将待添加的元素放入数组末尾;
- 将inset的encoding属性改为INTSET_ENC_INT32,将length属性改为4。


查找元素实现
intsetFind
函数用于在 intset
中查找一个特定的整数。由于 intset
是一个有序集合,因此可以使用二分查找算法来高效地查找元素。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| uint8_t intsetFind(intset *is, int64_t value) { uint8_t valenc = _intsetValueEncoding(value); return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is, value, NULL); }
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) { int min = 0, max = intrev32ifbe(is->length) - 1, mid = -1; int64_t cur = -1;
if (intrev32ifbe(is->length) == 0) { if (pos) *pos = 0; return 0; } else { if (value > _intsetGet(is, intrev32ifbe(is->length) - 1)) { if (pos) *pos = intrev32ifbe(is->length); return 0; } else if (value < _intsetGet(is, 0)) { if (pos) *pos = 0; return 0; } }
while (max >= min) { mid = ((unsigned int)min + (unsigned int)max) >> 1; 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; } }
|
步骤可以概括为:
- 检查目标值的编码类型是否与集合的编码类型匹配。
- 使用二分查找算法查找目标值。
- 如果找到目标值,返回
1
,并设置找到的位置。
- 如果未找到目标值,返回
0
,并设置插入位置。
时间复杂度为O(logn)
添加元素实现
intsetAdd
函数用于在 intset
中添加一个新元素。由于 intset
是一个有序集合,因此在添加新元素时需要保持其有序性。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| intset *intsetAdd(intset *is, int64_t value, uint8_t *success) { uint8_t valenc = _intsetValueEncoding(value); uint32_t pos; if (success) *success = 1;
if (valenc > intrev32ifbe(is->encoding)) { return intsetUpgradeAndAdd(is, value); } else { if (intsetSearch(is, value, &pos)) { if (success) *success = 0; return is; }
is = intsetResize(is, intrev32ifbe(is->length) + 1); if (pos < intrev32ifbe(is->length)) { intsetMoveTail(is, pos, pos + 1); } }
_intsetSet(is, pos, value); is->length = intrev32ifbe(intrev32ifbe(is->length) + 1); return is; }
|
步骤可以概括为:
- 检查是否需要升级编码。
- 检查值是否已存在。
- 扩展集合,为新元素腾出空间。
- 移动尾部元素,为新元素腾出位置。
- 插入新值。
- 更新集合的长度。
由于需要移动元素,最坏时间复杂度为O(n)
__END__