Redis 源码解析之通用双向链表(adlist
概述
adlist(A generic doubly linked list,作为一种通用的双向链表,用于简单的数据集合操作。adlist提供了基本的增删改查能力,并支持用户自定义深拷贝、释放和匹配操作来维护数据集合中的泛化数据 value
。
adlist 的数据结构
- 链表节点
listNode
, 作为双向链表, prev
, next
指针分别指向前序和后序节点。void*
指针类型的 value
用于存放泛化的数据类型(如果数据类型的 size 小于 sizeof(void*
, 则可直接存放在 value
中。 否则 value
存放指向该泛化类型的指针。
// in adlist.h
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
- 链表迭代器
listIter
, 其中next
指针指向下一次访问的链表节点。direction
标识当前迭代器的方向是AL_START_HEAD(从头到尾遍历
还是AL_START_TAIL(从尾到头遍历
。
// in adlist.h
typedef struct listIter {
listNode *next;
int direction;
} listIter;
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
- 双向链表结构
list
。 其中,head
和tail
指针分别指向链表的首节点和尾节点。len
记录当前链表的长度。函数指针dup
,free
和match
分别代表业务注册的对泛化类型value
进行深拷贝,释放和匹配操作的函数。(如果没有注册dup
, 则默认进行浅拷贝。 如果没有注册free
, 则不对value
进行释放。如果没有注册match
则直接比较value
的字面值
// in adlist.h
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;
adlist 的基本操作
-
创建:
listCreate
初始化相关字段为零值。可以通过 listSetDupMethod,
listSetFreeMethod
, listSetMatchMethod
来注册该链表泛化类型 value
的 dup
, free
和 match
函数。
/* Create a new list. The created list can be freed with
* listRelease(, but private value of every node need to be freed
* by the user before to call listRelease(, or by setting a free method using
* listSetFreeMethod.
*
* On error, NULL is returned. Otherwise the pointer to the new list. */
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;
}
#define listSetDupMethod(l,m ((l->dup = (m
#define listSetFreeMethod(l,m ((l->free = (m
#define listSetMatchMethod(l,m ((l->match = (m
-
在链表首插入新节点:
listAddNodeHead
-
(1 将新节点的
next
指向当前首节点(当前首节点将成为第二节点, 将会是新节点的后继节点
(2 将当前节点的prev
指向新节点, 新节点作为新的首节点将成为原首节点的前驱节点。
(3 将head
从原本指向旧的首节点改为指向新节点, 将新节点作为链表首。
(4 链表总计数加一
value 创建新节点,并让 list
的 head
和 tail
都指向新节点。
/* Add a new node to the list, to head, containing the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered.
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeHead(list *list, void *value
{
listNode *node;
if ((node = zmalloc(sizeof(*node == NULL
return NULL;
node->value = value;
listLinkNodeHead(list, node;
return list;
}
/*
* Add a node that has already been allocated to the head of list
*/
void listLinkNodeHead(list* list, listNode *node {
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++;
}
-
在链表尾插入新节点:
listAddNodeTail
- 在空链表插入新节点: 逻辑与
- 在非空链表插入新节点:
(1 将新节点的prev
指向当前首节点(当前尾节点将成为倒数第二节点, 将会是新节点的前驱节点
(2 将当前节点的next
指向新节点, 新节点作为新的尾节点将成为原尾节点的后继节点。
(3 将tail
从原本指向旧的尾节点改为指向新节点, 将新节点作为链表尾。
(4 链表总计数加一
listAddNodeHead
实现一致。
/* Add a new node to the list, to tail, containing the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered.
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeTail(list *list, void *value
{
listNode *node;
if ((node = zmalloc(sizeof(*node == NULL
return NULL;
node->value = value;
listLinkNodeTail(list, node;
return list;
}
/*
* Add a node that has already been allocated to the tail of list
*/
void listLinkNodeTail(list *list, listNode *node {
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++;
}
-
在链表指定位置插入
value
:listInsertNode
。如果after
为非零, 则将新节点作为old_node
后继节点。否则,新节点作为old_node
前驱节点。下图以after
为非零作为例子, 描述了这部分的代码逻辑。
(1 将新节点的prev
指向old_node
(新节点插入在old_node
之后;
(2 将新节点的next
指向old_node
的后继节点(old_node
的后继节点将成为新节点的后继节点;
(3 将old_node
的next
指向新节点;
(4 将新节点的后继节点的prev
指向新节点(old_node
的原后继节点现在成为了新节点的后继节点 。
(5 链表总计数加一
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;
}
-
删除链表指定节点:
listDelNode
。 下图以删除中间节点为例,展示了删除的流程。
(1 待删除节点的前驱节点的next
指向待删除节点的后继节点;
(2 待删除节点的后继节点的prev
指向待删除节点的前驱节点;
(3 待删除节点的next
和prev
都置为NULL
;
(4 链表总计数减一
(5 如果有注册free
函数,则用free
函数释放待删除节点的value
。然后释放待删除节点。
/* Remove the specified node from the specified list.
* The node is freed. If free callback is provided the value is freed as well.
*
* This function can't fail. */
void listDelNode(list *list, listNode *node
{
listUnlinkNode(list, node;
if (list->free list->free(node->value;
zfree(node;
}
/*
* Remove the specified node from the list without freeing it.
*/
void listUnlinkNode(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;
node->next = NULL;
node->prev = NULL;
list->len--;
}
5.链表的 Join 操作: listJoin
在链表l
的末尾添加列表o
的所有元素。 下图以两个链表都不为 NULL
的场景为例。
(1 o
的首部节点的 prev
指向 l
的尾部节点;
(2 l
的尾部节点的 next
指向 o
的首部节点(1,2 步将两个链表链接起来;
(3 l
的 tail
指向 o
的 tail
(o
的 tail
作为新链表的尾部;
(4 l
链表总计数加一;
(5 (6 清空 o
链表的信息;
/* Add all the elements of the list 'o' at the end of the
* list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o {
if (o->len == 0 return;
o->head->prev = l->tail;
if (l->tail
l->tail->next = o->head;
else
l->head = o->head;
l->tail = o->tail;
l->len += o->len;
/* Setup other as an empty list. */
o->head = o->tail = NULL;
o->len = 0;
}
- 其他函数: 其他函数实现较为简单,这里简单罗列一下,感兴趣的可以去看下源码。
// 获取 list 的迭代器
listIter *listGetIterator(list *list, int direction;
// 返回迭代器的下一个元素,并将迭代器移动一位。如果已遍历完成, 则返回 NULL
listNode *listNext(listIter *iter;
// 释放迭代器资源
void listReleaseIterator(listIter *iter;
// 拷贝链表
list *listDup(list *orig;
// 在链表中查找与 key 匹配的 value 所在的第一个节点。
// 如果不存在,则返回 NULL。
// 匹配操作由 list->match 函数提供。
// 如果没有注册 match 函数, 则直接比较 key 是否与 value 相等。
listNode *listSearchKey(list *list, void *key;
// 返回指定的索引的元素。 如果超过了链表范围, 则返回 NULL。
// 正整数表示从首部开始计算。
// 0 表示第一个元素,1 表示第二个元素, 以此类推。
// 负整数表示从尾部开始计算。
// -1 表示倒数第一个元素, -2 表示倒数第二个元素,以此类推。
listNode *listIndex(list *list, long index;
// 返回链表初始化的正向迭代器
void listRewind(list *list, listIter *li;
// 返回链表初始化的反向迭代器
void listRewindTail(list *list, listIter *li;
// 将链表尾部节点移到首部
void listRotateTailToHead(list *list;
// 将链表首部节点移到尾部
void listRotateHeadToTail(list *list;
// 用 value 初始化节点
void listInitNode(listNode *node, void *value;
adlist 的使用 demo
git@github.com:younglionwell/redis-adlist-example.git