在 Redis 的 Set 类型底层实现中,intset 和 hashtable 是两种最常见的编码方式。理解它们之间的转换条件,不仅是面试中的高频考点,也是掌握 Redis 内存优化思想的关键。本文将从底层结构出发,系统梳理 intset 与 hashtable 的转换触发条件,并延伸讨论相关的配置参数与实战建议。
一、先搞清楚:Set 类型到底有几种编码?
Redis 的 Set 类型底层并不是只有一种数据结构,而是根据元素特征动态选择编码,常见的有:
- intset(整数集合):当集合中所有元素都是整数,且元素数量不超过
set-max-intset-entries时使用。 - hashtable(字典):当集合中包含非整数元素,或整数元素数量超过阈值时使用。
- listpack(旧版本为 ziplist):Redis 7.2 之前,当集合元素较少且元素为字符串时,可能使用 listpack/ziplist 编码。需要注意,从 Redis 7.2 开始,Set 类型不再使用 listpack 编码,小集合也直接使用 hashtable。
因此,讨论 intset 和 hashtable 的转换,核心是围绕整数集合何时退化为字典这一问题展开。
二、intset 的结构回顾
intset 是 Redis 为整数集合专门设计的一种紧凑结构,定义大致如下:
typedef struct intset {
uint32_t encoding; // 编码方式:int16 / int32 / int64
uint32_t length; // 元素个数
int8_t contents[]; // 真正存储数据的柔性数组
} intset;
它的特点非常鲜明:
- 内存紧凑:所有元素按升序排列,连续存储在一块内存中,没有指针开销。
- 支持升级:当插入的整数超出当前编码范围时,会触发整体升级(如 int16 升级为 int32),但不支持降级。
- 查找高效:由于有序,可以使用二分查找,时间复杂度为 O(log N)。
正是这些特点,使得 intset 在存储纯整数小集合时,比 hashtable 节省大量内存。
三、intset 转换为 hashtable 的条件
这是面试中最核心的问题。触发转换的条件主要有两个:
条件一:插入非整数元素
intset 只能存储整数。一旦向集合中插入任何非整数元素(如字符串 "hello"),intset 就无法继续使用,Redis 会立即将其转换为 hashtable。
例如:
SADD nums 1 2 3 # 使用 intset
SADD nums "abc" # 触发转换为 hashtable
条件二:元素数量超过 set-max-intset-entries
即使集合中全部是整数,当元素个数超过配置项 set-max-intset-entries 的限制时,也会转换为 hashtable。
该配置的默认值为:
set-max-intset-entries 512
也就是说,当整数集合的元素数量超过 512 个时,Redis 会将其编码从 intset 转换为 hashtable。
需要特别注意的是:这个判断发生在插入操作时。当插入第 513 个整数元素时,转换被触发。
四、转换的源码逻辑
在 Redis 源码 t_set.c 中,相关逻辑大致如下:
if (setTypeSize(set) > server.set_max_intset_entries) {
setTypeConvert(set, OBJ_ENCODING_HT);
}
以及插入非整数时的判断:
if (set->encoding == OBJ_ENCODING_INTSET &&
!string2ll(value, sdslen(value), &llval)) {
setTypeConvert(set, OBJ_ENCODING_HT);
}
可以看到,转换是单向的:intset → hashtable,一旦转换完成,即使后续删除了所有非整数元素,或者元素数量降到阈值以下,也不会回退为 intset。这一点与 ziplist/listpack 的转换规则类似,都是为了避免频繁的结构抖动。
五、与 listpack 编码的关系(补充说明)
在 Redis 7.2 之前,Set 还有一个 set-max-listpack-entries(旧版为 set-max-ziplist-entries)和 set-max-listpack-value 配置,用于控制小字符串集合使用 listpack 编码。其转换条件为:
- 元素数量超过
set-max-listpack-entries(默认 128); - 或插入的元素长度超过
set-max-listpack-value(默认 64)。
但如前所述,Redis 7.2 起已移除 Set 的 listpack 编码,小集合直接使用 hashtable。因此在新版本中,Set 的编码实际上只剩 intset 和 hashtable 两种。
六、面试常见追问
追问 1:为什么 intset 转 hashtable 后不能转回来?
答:主要是为了避免在元素频繁增删时反复进行结构转换,造成性能抖动。同时,转换本身涉及内存重新分配和数据迁移,代价较高。
追问 2:intset 的升级和 Set 编码转换有什么区别?
答:intset 升级(int16→int32→int64)是 intset 内部编码的变化,仍然是 intset;而 intset 转 hashtable 是整体数据结构的变化,两者不是一回事。
追问 3:如何查看一个 Set 的当前编码?
答:
OBJECT ENCODING myset
返回 intset 或 hashtable。
追问 4:set-max-intset-entries 设置得越大越好吗?
答:不一定。设置得越大,intset 能容纳的整数越多,内存越省;但 intset 的插入和删除涉及内存重分配(尤其是升级时),过大可能导致单次操作延迟升高。需要根据业务场景权衡。
七、实战建议
- 纯整数集合尽量控制在 512 以内,以享受 intset 的内存优势。
- 避免在整数集合中混入字符串,否则会提前触发 hashtable 转换,内存占用显著上升。
- 关注 Redis 版本差异,7.2 前后 Set 的编码策略有变化,升级时需留意。
- 使用
OBJECT ENCODING定期检查关键 Set 的编码,及时发现意外的转换。
八、小结
| 转换方向 | 触发条件 |
|---|---|
| intset → hashtable | 插入非整数元素 |
| intset → hashtable | 元素数量超过 set-max-intset-entries(默认 512) |
| hashtable → intset | 不支持回退 |
掌握 intset 与 hashtable 的转换条件,本质上是理解 Redis “以内存换效率、以结构换紧凑”的设计哲学。在面试中,能够说清触发条件、源码位置以及不可逆的原因,基本就能拿到这道题的高分。
未经允许不得转载:任鹏个人博客 » Redis 的 intset 和 hashtable 编码转换条件

