redis双向链表源码解析(adlist.h/adlist.c)
数据结构
listNode是一个双向链表节点结构,每个节点存的数据用void*指向。
listIter是一个双向链表迭代器,其中direction用来判断迭代方向
list表示双向链表结构,保存着头节点,尾节点,提供三个函数指针, 供用户传入自定义函数, 用于复制(dup)、释放(free)和匹配(match)链表中的结点的值(value); 通过无符号长整数len, 标示链表的长度。
/*
* 双端链表节点
*/
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
/*
* 双端链表迭代器
*/
typedef struct listIter {
// 当前迭代到的节点
listNode *next;
// 迭代的方向
int direction;
} listIter;
/*
* 双端链表结构
*/
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;
创建list函数listCreate
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;
}
一定要在堆中分配内存,注意函数返回值是指向list的指针
释放一个list:listRelease
void listRelease(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;
}
// 释放链表结构
zfree(list);
}
在释放节点时,如果有设置释放函数,则使用释放函数释放,该函数没有返回值
list添加一个节点到Head:listAddNodeHead
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;
}
注意:添加到非空链表时,是添加到链表的头部,所以list->head需要有所调整。
list添加一个节点到Head:listAddNodeTail
list *listAddNodeTail(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 = list->tail;
node->next = NULL;
list->tail->next = node;
list->tail = node;
}
// 更新链表节点数
list->len++;
return list;
}
添加节点别忘了改变list->list
添加节点到中间listInsertNode(复杂度o(1))
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
listNode *node;
// 创建新节点
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
// 保存值
node->value = value;
// 将新节点添加到给定节点之后
if (after) {
node->prev = old_node;
node->next = old_node->next;
// 给定节点是原表尾节点
if (list->tail == old_node) {
list->tail = node;
}
// 将新节点添加到给定节点之前
} else {
node->next = old_node;
node->prev = old_node->prev;
// 给定节点是原表头节点
if (list->head == old_node) {
list->head = node;
}
}
// 更新新节点的前置指针
if (node->prev != NULL) {
node->prev->next = node;
}
// 更新新节点的后置指针
if (node->next != NULL) {
node->next->prev = node;
}
// 更新链表节点数
list->len++;
return list;
}
该函数是个入参1.list 2.标记节点 3.插入的value 4.after,是插入标记的前面还是后面
删除节点listDelNode
void listDelNode(list *list, listNode *node)
{
// 调整前置节点的指针
if (node->prev)
node->prev->next = node->next;
else
list->head = node->next;
// 调整后置节点的指针
if (node->next)
node->next->prev = node->prev;
else
list->tail = node->prev;
// 释放值
if (list->free) list->free(node->value);
// 释放节点
zfree(node);
// 链表数减一
list->len--;
}
注意需要判断一下list是否为空。如果有释放函数就调释放函数,如果没有,就使用zfree.
创建迭代器:listGetIterator
listIter *listGetIterator(list *list, int direction)
{
// 为迭代器分配内存
listIter *iter;
if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
// 根据迭代方向,设置迭代器的起始节点
if (direction == AL_START_HEAD)
iter->next = list->head;
else
iter->next = list->tail;
// 记录迭代方向
iter->direction = direction;
return iter;
}
1.为其分配内存 2.根据迭代器方向,选择迭代器next的指向 3.返回迭代器指针
复制整个list : listDup
list *listDup(list *orig)
{
list *copy;
listIter *iter;
listNode *node;
// 创建新链表
if ((copy = listCreate()) == NULL)
return NULL;
// 设置节点值处理函数
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match;
// 迭代整个输入链表
iter = listGetIterator(orig, AL_START_HEAD);
while((node = listNext(iter)) != NULL)
{
void *value;
// 复制节点值到新节点
if (copy->dup)
{
value = copy->dup(node->value);
if (value == NULL)
{
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
}
else
value = node->value;
// 将节点添加到链表
if (listAddNodeTail(copy, value) == NULL)
{
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
}
// 释放迭代器
listReleaseIterator(iter);
// 返回副本
return copy;
}
这里面调用了多个前面描述过的函数listCreate,listGetIterator,listNext,listRelease,listReleaseIterator,listAddNodeTail,这段代码看起来也相对轻松。并且用到了指针函数dup
查找list与key匹配的节点
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;
listNode *node;
// 迭代整个链表
iter = listGetIterator(list, AL_START_HEAD);
while((node = listNext(iter)) != NULL)
{
// 对比
if (list->match)
{
if (list->match(node->value, key))
{
listReleaseIterator(iter);
// 找到
return node;
}
}
else
{
if (key == node->value)
{
listReleaseIterator(iter);
// 找到
return node;
}
}
}
listReleaseIterator(iter);
// 未找到
return NULL;
}
list->match(node->value, key)调用match匹配函数,比较value与key,如果找到了,就返回当前节点指针。
索引函数:listIndex
listNode *listIndex(list *list, long index) {
listNode *n;
// 如果索引为负数,从表尾开始查找
if (index < 0) {
index = (-index)-1;
n = list->tail;
while(index-- && n) n = n->prev;
// 如果索引为正数,从表头开始查找
} else {
n = list->head;
while(index-- && n) n = n->next;
}
return n;
}
需要注意的是,该函数接受负数,索引从0开始
使用方法
Redis定义了一系列的宏,用于访问list及其内部结点。
链表创建时(listCreate), 通过Redis自己实现的zmalloc()分配堆空间。 链表释放(listRelease)或删除结点(listDelNode)时, 如果定义了链表(list)的指针函数free, Redis会使用它释放链表的每一个结点的值(value), 否则需要用户手动释放。 结点的内存使用Redis自己实现的zfree()释放。
对于迭代器, 通过方法listGetIterator()、listNext()、 listReleaseIterator()、listRewind()和listRewindTail()使用, 例如对于链表list,要从头向尾遍历,可通过如下代码:
iter = listGetIterator(list, AL_START_HEAD); // 获取迭代器
while((node = listNext(iter)) != NULL)
{
dosomething;
}
listReleaseIterator(iter);
listDup()用于复制链表, 如果用户实现了dup函数, 则会使用它复制链表结点的value。 listSearchKey()通过循环的方式在O(N)的时间复杂度下查找值, 若用户实现了match函数, 则用它进行匹配, 否则使用按引用匹配。
如果你对链表比较熟悉,看这段代码应该很简单,如果不了解数据机构链表,可以先学习一下链表,然后学习redis的list源码。