Redis Intset(整数集合)技术文档
一、概述
Intset(整数集合)是 Redis 中Set 数据类型的底层编码之一 ,用于存储整数集合。当 Set 中的元素都是整数且数量较少时,Redis 会使用 Intset 作为底层实现。
1.1 设计思想
"用最小的空间存储整数,自动升级编码以适应更大的整数"
1.2 使用场景
- Set 类型:当所有元素都是整数且数量较少时
- 元素范围:取决于编码(int16/int32/int64)
二、数据结构
c
typedef struct intset {
uint32_t encoding; // 编码类型
uint32_t length; // 元素数量
int8_t contents[]; // 柔性数组,存储实际数据
} intset;
2.1 内存布局
scss
+-----------+-----------+---------------------------+
| encoding | length | contents[] |
| (4 bytes) | (4 bytes) | (length * encoding bytes) |
+-----------+-----------+---------------------------+
| 字段 | 类型 | 说明 |
|---|---|---|
encoding |
uint32_t |
元素编码(INT16/INT32/INT64) |
length |
uint32_t |
元素数量 |
contents |
int8_t[] |
实际存储的数据(柔性数组) |
2.2 编码类型
c
#define INTSET_ENC_INT16 (sizeof(int16_t)) // 2 bytes
#define INTSET_ENC_INT32 (sizeof(int32_t)) // 4 bytes
#define INTSET_ENC_INT64 (sizeof(int64_t)) // 8 bytes
| 编码 | 大小 | 范围 |
|---|---|---|
INTSET_ENC_INT16 |
2 bytes | -32768 ~ 32767 |
INTSET_ENC_INT32 |
4 bytes | -2^31 ~ 2^31-1 |
INTSET_ENC_INT64 |
8 bytes | -2^63 ~ 2^63-1 |
三、核心特性
3.1 有序存储
Intset 中的元素按升序排列 ,支持二分查找。
diff
+---+---+---+---+---+---+
| 1 | 3 | 5 | 7 | 9 | 11|
+---+---+---+---+---+---+
3.2 自动升级
当插入一个超出当前编码范围的整数时,Intset 会自动升级编码:
sql
Before: INT16 encoding
+-----+-----+-----+
| 100 | 200 | 300 |
+-----+-----+-----+
Insert: 40000 (超出 INT16 范围)
After: INT32 encoding (自动升级)
+-----+-----+-----+-----+
| 100 | 200 | 300 |40000|
+-----+-----+-----+-----+
升级过程:
- 重新分配内存(新编码大小 × 元素数)
- 从后向前复制数据(避免覆盖)
- 插入新元素
3.3 不重复
Intset 保证元素不重复,插入时会先查找。
四、核心 API
4.1 创建与销毁
c
intset *intsetNew(void); // 创建空 intset
4.2 插入与删除
c
// 插入元素(自动去重)
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
// 删除元素
intset *intsetRemove(intset *is, int64_t value, int *success);
4.3 查询操作
c
// 查找元素
uint8_t intsetFind(intset *is, int64_t value);
// 获取指定位置的值
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
// 获取随机值
int64_t intsetRandom(intset *is);
// 获取最大值
int64_t intsetMax(intset *is);
// 获取最小值
int64_t intsetMin(intset *is);
// 获取长度
uint32_t intsetLen(const intset *is);
五、核心算法
5.1 二分查找
c
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;
}
if (value > _intsetGet(is, max)) {
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; // 未找到
}
}
5.2 编码升级
c
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
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));
}
// 插入新值
if (prepend) {
_intsetSet(is, 0, value);
} else {
_intsetSet(is, intrev32ifbe(is->length), value);
}
is->length = intrev32ifbe(intrev32ifbe(is->length) + 1);
return is;
}
六、性能分析
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 查找 | O(log n) | 二分查找 |
| 插入 | O(n) | 插入位置后移 + 可能升级 |
| 删除 | O(n) | 删除位置前移 |
| 获取长度 | O(1) | 直接读取 |
| 获取最大/小值 | O(1) | 首尾元素 |
七、在 Redis 中的应用
c
// 当 Set 满足以下条件时使用 Intset:
// 1. 所有元素都是整数
// 2. 元素数量 < set-max-intset-entries (默认 512)
// 配置
set-max-intset-entries 512
参考源码 :Redis 8.8.2 (src/intset.h, src/intset.c)