概念
链表作为常用的数据结构,在很多系统中都会用到,Redis 底层实现了一个可以双向遍历的链表结构,Redis 上层数据类型的 List 键就会用到链表作为底层实现之一。
定义
Redis 中的链表结构分为 listNode (链表节点) 和 list (链表主体),链表节点定义如下:
typedef struct listNode {
// 前置节点
struct listNode * prev;
// 后置节点
struct listNode * next;
//节点的值
void * value;
}listNode;
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;
特点
-
双向遍历:通过节点持有的 prev 和 next 两个指针实现;
-
无环:表头节点 prev 和 表位节点的 next 指针都指向 NULL,便于判断遍历终点;
-
快速获取表头和表尾:list 结构体通过分别持有表头和表尾的两个指针,O(1) 复杂度即可取得表头或者表尾的元素;
-
快速获取表长:无需遍历所有节点,通过 list->len 可以在 O(1) 复杂度内获取表长度;
-
多态性:listNode->value 类型为 void* ,允许节点保存任意类型的数据
源码
源码中列表节点和链表主体的定义都在 https://github.com/redis/redis/blob/7.2.4/src/adlist.h#L36
链表节点:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
链表主体:
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len;
} list;
此外,还定义了一个专门用于遍历链表的结构体 listIter
:
typedef struct listIter {
listNode *next;
int direction;
} listIter;
再看一下 Redis 是怎么通过 listIter 来遍历列表:
// 从链表 list 中搜索某个 key
listNode *listSearchKey(list *list, void *key)
{
listIter iter;
listNode *node;
// 初始化,将遍历器指向表头
listRewind(list, &iter);
// 开始遍历
while((node = listNext(&iter)) != NULL) {
// 这里会用到了链表自带的 match 方法
// 如果有自定义的match方法就调用,否则就手动比较
if (list->match) {
if (list->match(node->value, key)) {
return node;
}
} else {
if (key == node->value) {
return node;
}
}
}
return NULL;
}
可以看出,Redis 的链表结构实现的比较经典,经过这么多版本的迭代,也没有做过大的改动,可见这个结构是足够健壮的。