Redis源码分析:数据结构之链表(list)
本节的分析基于redis-6.0.0
内容在 src/adlist.h 和 src/adlist.c 中
C语言没有内置链表这种数据结构,所以Redis实现了自己的链表。链表在Redis中的应用十分广泛,有必要来理解它的实现。
节点和链表的结构
链表节点由一个 listNode 结构体来表示:
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
listNode 是构建链表的组件,各个节点之间由 prev 和 next 指针相关联。
链表使用一个 list 结构体来表示:
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;
相关API的实现
从 adlist.h 可以看出,这些API 有一些是宏,剩下一些是函数。宏很简单,我们来讲讲这些函数是怎么实现的。
*list listCreate(void);
创建一个不包含任何节点的新链表
实现方式:
list *listCreate(void)
{
struct list *list;
if ((list = zmalloc(sizeof(*list))) == NULL)
return NULL;
list->head = list->tail = NULL;
list->len = 0;
list->dup = NULL;
list->free = NULL;
list->match = NULL;
return list;
}
代码比较简单,唯一值得注意的是,给链表分配内存空间使用的方式是Redis自己封装的。
*void listEmpty(list list);
清空链表,但不删除该链表
实现方式:
void listEmpty(list *list)
{
unsigned long len;
listNode *current, *next;
current = list->head;
len = list->len;
while(len--) {
next = current->next;
if (list->free) list->free(current->value);
zfree(current);
current = next;
}
list->head = list->tail = NULL;
list->len = 0;
}
需要注意的是,zfree也是Redis自己封装的。
*void listRelease(list list);
释放给定的链表,以及链表中的所有节点
实现方式:
void listRelease(list *list)
{
listEmpty(list);
zfree(list);
}
**list *listAddNodeHead(list list, void value);
将一个包含给定值的新节点添加到给定链表的表头
实现方式:
list *listAddNodeHead(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
添加的时候,分两种情况:给空链表添加表头;给非空链表添加表头。