五大数据类型的底层物理映射全景图
为什么 Redis 快?因为它针对每一个上层你用的数据类型(String、List、Hash、Set、ZSet),都在底层偷偷安排了两套甚至多套备用武器(物理数据结构)。
它会像一个极其抠门的管家一样,根据你塞进去的数据规模,动态无缝地帮你切换底层结构。
1. String 的唯一物理支撑
- 底层映射:毫无争议,全部映射为 SDS (简单动态字符串)。
- (虽然 SDS 内部也会看人下菜碟,细分为
sdshdr5/8/16...来抠内存)。
- (虽然 SDS 内部也会看人下菜碟,细分为
2. List (列表) 的演进
- 早期映射:数据少用
ZipList,大了用双向链表。 - 现代化统一:目前已经被强制统一映射为 QuickList(里面装载了
ListPack)。由于 QuickList 兼顾了连续微观内存和宏观分段的优势,它目前是绝对的霸主。
3. Hash (字典/哈希) 的动态切局
- 微量数据:映射为 ListPack。为什么不用哈希表?当你购物车里只有 5 件商品时,直接在一个贴在一起的小数组里线性找反而最快,省去了哈希函数的计算和指针开销。
- 一旦庞大:瞬间打散,升级成正规的 Dict (哈希字典),靠 O(1) 暴力查。
4. Set (集合):被发扬光大的 IntSet
普通集合正常来说只有键没有值,按理说用 Dict (哈希字典) 存就够了。但 Redis 在这里单独开了一个令人发指的极简通道:
- 全都是整数时:映射为专用兵器 IntSet (整数集合)。它极致地把小数字整整齐齐排在连续的一字型内存里,查找直接用二分法,连哈希都省了!省内存到了骨子里。
- 掺杂了其他元素:一旦里面插了一个字符串,或者数字实在太多,才会乖乖转为 Dict (哈希字典) 去托管。
5. ZSet (有序集合):双修怪兽
有序集合是整个 Redis 结构里最为特殊和豪华的变异体。
- 数据量极小(比如微型排行榜):底层会妥协,使用 ListPack,直接顺序硬排。
- (疑问:为什么这层不用极其省钱的 IntSet? 答:因为 IntSet 定死了只认准数字。但排行榜的结构是
[用户名(Member)][积分(Score)]绑在一起的,只能靠有混合包裹能力的 ListPack 收留。)
- (疑问:为什么这层不用极其省钱的 IntSet? 答:因为 IntSet 定死了只认准数字。但排行榜的结构是
- 标准完整态(海量排行):映射为 SkipList (跳表) + Dict (字典) 双轨并行!
- Dict 负责:提供 的单个成员分数精准查询(比如查询用户 A 的具体积分究竟是多少)。
- SkipList 负责:承担范围搜索(查前一百名)以及由于它身带
span(跨度) 而带来的恐怖 级别全量排位换算。 - 注意:虽然表面上看占用了两套神级武器,但它们存的仅仅是指向相同元素堆的内存指针而已,底层用牺牲几十个字节的指针来换取绝对的速度,这就是极致的“空间换时间”!
🧐 进阶三大件:特殊数据类型的“伪装术”与“黑科技”
除去基础的五大类型,Redis 后来应对高阶实战引入了三大特殊类型。它们的底层映射更是展现了 Redis “能白嫖就绝不造轮子,非要造就造最强的” 的极致思想:
6. BitMap (位图):借壳上市
- 底层映射:直接白嫖 String (SDS)!
- 内幕:BitMap 压根就不是一个独立的数据结构。因为 SDS 天然是二进制安全的,且 Redis 强制规定一个 String 最大可以达到 512 MB。
- 神级巧合:512 MB 换算成位,刚好等于 个 bit,也就是可以存 42 亿 个
0或1(刚好覆盖全球网民或者全网 UUID 散列)。所以 Redis 直接拿 SDS 底层的buf[]字节数组当成一个无比巨大的 Boolean 数组去操纵位运算(SETBIT/GETBIT)。
7. HyperLogLog (基数统计):极致压缩的魔术
- 底层映射:同样是白嫖的 String (SDS)。
- 内幕:HyperLogLog 是一种用于海量去重统计(比如估算几亿的 UV)的算法。在 Redis 内部,它利用极度变态的数学桶分配算法,把好几亿的数据量压缩成了 16384 个桶(每个桶用 6 bit 记录)。
- 为什么映射成 SDS:16384 * 6 bit 算下来最多只占 12 KB!所以 Redis 直接建了一个区区 12 KB 长度的 String 来存储这堆数学状态。在底层视角看来,它就是一个极小的 SDS 而已,完全复用了普通的内存管理机制。
8. Stream (消息队列):Radix Tree 与 紧凑列表的终极缝合
(Redis 5.0 划时代核心数据结构)
- 底层映射:为了 Stream,Redis 作者专门打造了一个极其庞大且严密的结构—— Rax (基数树/字典树) + ListPack (紧凑列表)。
- 为什么这么复杂?:消息队列的痛点在于消息 ID 是高度重复的(比如用时间戳生成的
168888888-1,168888888-2)。- 如果全存下来,前缀冗余极高。所以 Redis 用 Rax (基数树) 来作底层索引,完美把相同前缀的时间戳压扁,共享内存节点。
- 而在具体装载一群连通的大批量消息时,它的每一个树节点(MacroNode)内部,装的其实是一整块 ListPack,靠这辆连续高密度的“大巴车”来装载消息内容体系。
- 这是 Redis 发展到后期,最巅峰的复合数据结构运用。