作为一种常用数据结构,链表内置在很多高级编程语言里, 因为 C 语言没有内置这种数据结构, Redis 构建了自己的链表实现.
链表在 Redis 中的应用十分广泛, 比如列表键的底层实现之一就是链表. 当一个列表键包含了数量较多的元素,或者列表中包含的元素都是比较长的字符串时, Redis 就会使用链表作为列表键的底层实现.
除了列表键之外, 发布与订阅、慢查询、监视器等功能也用到了链表.还有使用链表来构建客户端输出缓冲区等.
对于链表的定义和相关算法,很多算法书籍已经做了详细的讲解.这里只对 Redis 中链表的实现做介绍.
链表和链表节点的实现
每个链表节点使用一个 listNode
结构来表示:
// file: adlist.h
typedef struct listNode {
struct listNode *prev; // 前置节点
struct listNode *next; //后置节点
void *value; //节点的值
} ListNode;
多个链表节点通过 prev
和 next
组成双端链表,
Redis 使用list
结构来管理链表:
typedef struct list {
listNode *head; //表头节点
listNode *tail; //表尾节点
unsigned long len; //链表的节点数量
void *(*dup) (void *ptr); //节点值复制函数
void (*free)(void *ptr); //节点值释放函数
int (*match)(void *ptr, void *key); //节点值对比函数
} list;
下图是由一个 lsit
结构和三个ListNode
结构组成的链表.
链表实现特性
Redis 的链表实现特性可以总结如下:
- 双端: 因为带有
prev
和next
指针,获取某个节点的前置节点和后置节点的复杂度都是 O(1) - 无环: 表头的
prev
指针和表尾的next
指针均指向NULL
, 对链表的访问以NULL
结束. - 带表头指针和表尾指针: 通过
list
结构获取表头和表尾的复杂度为 O(1) . - 带链表长度计数器:
list
结构的len
属性对链表节点的长度计数, 获取链表中节点的数量复杂度为 O(1) - 多态: 链表节点使用
void *
指针来保存节点值, 并且可以通过 list 结构的dup
、free
、match
三个属性为节点值设置类型特定函数.链表可以用于保存各种不同类型的值.