Redis 的链表:像智能文件夹一样灵活的列表结构
目录
一、从传统数组的痛点到链表的优势
二、链表的核心结构:文件夹与活页的组合
2.1、 list 结构体:文件夹封面
2.2、listNode 结构体:活页纸
2.2.1、为什么用 void*?万能收纳袋的秘密
2.2.2、多态带来的灵活性
2.2.3、链表与 SDS 的完美配合
1. 列表键名的存储
2. 字符串类型节点的存储
2.2.4、三大工具函数:链表的智能管家
1. dup 函数:内容复印机
2. free 函数:内容清理器
3. match 函数:内容查找器
三、Redis 链表的核心优势
链表在 Redis 中的典型应用
总结:链表与 SDS 的协同智慧
如果说 SDS 是 Redis 的 "智能笔记本",那链表(list)就是把这些笔记本有序管理起来的 "智能文件夹系统"。当你执行lpush list 0 1 2 "hello" 'a'这样的命令时,Redis 就像在创建一个带多个口袋的文件夹,每个口袋里装着不同类型的笔记本。
一、从传统数组的痛点到链表的优势
想象你用两种方式整理文件:
- 传统数组像固定格子的抽屉:格子大小固定,想增减文件必须整个抽屉重建;要知道有多少文件得逐个清点;想在中间插入文件得挪动后面所有文件。
- Redis 链表像活页文件夹:可以随时增减页面,不用重建整个文件夹;封面标着文件总数;页面间用活页环连接,插入删除只需调整活页环。
这就是为什么 Redis 列表(list)要采用链表结构 —— 它完美解决了动态数据管理的核心痛点。
二、链表的核心结构:文件夹与活页的组合
Redis 的链表结构由两个关键部分组成,就像文件夹系统的 "封面" 和 "活页":
2.1、 list 结构体:文件夹封面
struct list {listNode *head; // 第一个活页(表头节点)listNode *tail; // 最后一个活页(表尾节点)unsigned long len; // 活页总数(节点数量)void *(*dup)(void *ptr); // 复制工具(节点值复制函数)void (*free)(void *ptr); // 销毁工具(节点值释放函数)int (*match)(void *ptr,void *key); // 查找工具(节点值对比函数)};
这个结构体就像文件夹封面,记录着:
- 第一页和最后一页的位置(head 和 tail)
- 总共有多少页(len)
- 三套管理工具(复制、销毁、查找函数)
2.2、listNode 结构体:活页纸
struct listNode {struct listNode *prev; // 前一页(前置节点)struct listNode *next; // 后一页(后置节点)void *value; // 页面内容(节点的值)};
每个节点就像一张活页纸:
- 左右两边的装订孔(prev 和 next)连接着前后页面
- 中间的内容区(value)可以放任何类型的内容
2.2.1、为什么用 void*?万能收纳袋的秘密
你可能会好奇,为什么 value 要用void*类型?这正是 Redis 链表的精妙之处 ——实现多态存储。
- 就像超市的购物袋(void*)可以装苹果、牛奶、纸巾等任何商品,不管它们是固体还是液体
- 就像 Java 的 Object 类能接收任何类型的对象,Redis 用void*让链表节点能存储任何类型的数据
- 当你存入数字 0 时,value 指向整数存储区;存入 "hello" 时,它指向 SDS 结构体
2.2.2、多态带来的灵活性
执行lpush list 0 1 2 "hello" 'a'后,链表会形成这样的结构:
- 第一个节点的 value 指向整数 0
- 第二个节点的 value 指向整数 1
- 第三个节点的 value 指向整数 2
- 第四个节点的 value 指向 SDS 结构体(存储 "hello")
- 第五个节点的 value 指向 SDS 结构体(存储 "a")
就像一个文件夹里既能放笔记本,又能放照片、名片,这种灵活性让 Redis 列表能适应各种场景。
2.2.3、链表与 SDS 的完美配合
SDS 在链表中扮演着双重角色,就像文件夹系统中的两种重要载体:
1. 列表键名的存储
列表名称 "list" 本身用 SDS 存储:
len=4, free=4, buf=['l','i','s','t','\0']
这就像文件夹封面上的标签,用 SDS 存储既方便修改名称,又能快速获取长度。
2. 字符串类型节点的存储
当节点值是字符串(如 "hello"、"a")时,这些值会用 SDS 存储,然后把 SDS 的指针交给 listNode 的 value:
// "hello"节点的结构关系
listNode.value → sdshdr {
len=5,
free=5,
buf=['h','e','l','l','o','\0']
}
这种组合让链表同时拥有了:
- 链表的动态管理能力
- SDS 的字符串优化特性
2.2.4、三大工具函数:链表的智能管家
list 结构体中的三个函数指针就像文件夹的智能管理工具,处理不同类型内容时会自动适配:
1. dup 函数:内容复印机
- 当复制整数节点时,它会创建新的整数副本
- 当复制 SDS 节点时,它会调用sdsdup函数复制整个字符串
- 类比:就像复印机知道复印照片用彩色模式,复印文档用黑白模式
2. free 函数:内容清理器
- 释放整数节点时直接回收内存
- 释放 SDS 节点时会调用sdsfree函数,正确清理字符串空间
- 类比:就像垃圾桶知道纸类和塑料需要不同的处理方式
3. match 函数:内容查找器
- 比较整数时直接对比数值
- 比较字符串时会调用sdslen和sdscmp等函数
- 类比:就像查找文件时,知道按文件名找文本,按尺寸找图片
三、Redis 链表的核心优势
特性 | 生活比喻 | 技术价值 |
双向指针 | 活页纸的前后连接 | 支持 O (1) 时间复杂度的首尾操作 |
len 属性 | 文件夹的页数标记 | 快速获取长度(O (1) 复杂度) |
void* value | 万能收纳袋 | 支持多类型数据存储 |
函数指针 | 智能处理工具 | 适配不同类型的操作需求 |
与 SDS 结合 | 专业载体 + 灵活管理 | 兼顾字符串优化和动态管理 |
链表在 Redis 中的典型应用
- 消息队列:用lpush生产消息,rpop消费消息,链表的首尾操作效率极高
- 最新列表:用lpush添加新元素,lrange获取最新 N 条,适合记录日志、动态
- 分页查询:利用链表的有序性,通过lindex访问指定位置元素
就像多功能文件夹既能做待办清单,又能做日记本,Redis 链表的灵活性让它成为许多场景的理想选择。
总结:链表与 SDS 的协同智慧
Redis 的链表设计体现了 "组合优于继承" 的设计哲学:
- 用链表解决动态管理问题
- 用 SDS 解决字符串存储问题
- 用 void * 和函数指针解决多态问题
这种结构让 Redis 列表既能高效处理百万级数据,又能灵活支持各种数据类型,成为 Redis 五大基本数据结构中最灵活的成员之一。就像一套精心设计的文具系统,每个组件各有所长又完美配合,共同支撑起 Redis 的高性能表现。