链表作为算法基础,相信大家都不会陌生。链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活的调整链表的长度。Redis的链表是一个无环双向链表结构。
涉及的主要代码文件:
adlist.h
adlist.c
list的定义
typedef struct listNode {
struct listNode *prev; //指向前一个节点,头节点,该值为null
struct listNode *next; //指向后一个节点,尾节点,指向后一个null
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; //记录链表长度,O(1)获取
} list;
//迭代器
typedef struct listIter {
listNode *next;
int direction; //遍历方向
} listIter;
双向列表由listNode
组成,listNode
包含了指向前后节点地址(pre
和next
),其中,链表头结点的pre
和尾节点的next
指向NULL
。
list
结构为链表提供了链表的表头指针head
,表尾指针tail
,可以从前遍历链表,也可以从后遍历链表;包含了链表长度计数器len
,因此获取链表长度的时间复杂度为O(1);还包含了dup
、free
、match
函数指针用来实现多态链表。
-
dup
函数用来复制表节点所保存的值; -
free
函数用来释放表节点所保存的值; -
match
函数用来比较节点保存的值和传入值的大小。
listIter
是用来遍历链表的迭代器,其中保存了要访问的下一个节点(next
)和迭代遍历的方向(direction
)。
list的关键实现细节
/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)
定义了访问list
属性的一些宏函数。
//创建链表对象
list *listCreate(void)
{
struct list *list;
if ((list = zmalloc(sizeof(*list))) == NULL) //创建list
return NULL;
list->head = list->tail = NULL;
list->len = 0;
list->dup = NULL;
list->free = NULL;
list->match = NULL;
return list;
}
/* Free the whole list.
*
* This function can't fail. */
//释放整个链表
void listRelease(list *list)
{
unsigned long len;
listNode *current, *next;
current = list->head;
len = list->len;
while(len--) { //一个个的释放listNode
next = current->next;
if (list->free) list->free(current->value);
zfree(current);
current = next;
}
zfree(list); //释放list
}
创建和销毁链表。
//添加新元素到链表头部
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; //还要改变原头部节点的pre指针
list->head = node;
}
list->len++;
return list;
}
//添加元素到链表尾部
list *listAddNodeTail(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
//要考虑节点长度为0的情况
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;
}
//插入值到指定节点位置,after表示之前,还是之后。需要传入list,因为可能涉及头部、尾部节点的更改
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;
}
链表插入的相关函数。
//获取遍历迭代器
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;
}
/* Release the iterator memory */
//释放迭代器
void listReleaseIterator(listIter *iter) {
zfree(iter);
}
//迭代,获取迭代器指向的下一个元素
listNode *listNext(listIter *iter)
{
listNode *current = iter->next;
if (current != NULL) {
if (iter->direction == AL_START_HEAD)
iter->next = current->next;
else
iter->next = current->prev;
}
return current;
}
迭代器相关函数。
//链表复制函数
/*
注意点:1. 使用list->dup函数复制值,若不存在则新链表与旧链表共用value
2. 中间若有分配空间失败,要释放已分配的链表空间
*/
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;
}
链表复制。
总结
链表相对比较简单,可以记录的东西并不多。
Redis的特性:
-
list
有head
和tail
指针,因此可以正向和反向遍历链表; -
list
中存有链表长度属性len
,因此获取链表长度的时间复杂度为O(1); -
ListNode
使用void *
来保存链表的值,同时提供值操作函数dup
、free
、match
,为节点值设置指定类型操作函数,因此可以使用链表存储不同类型的值(同一链表的值类型相同)。
List主要API
function | description | time complexity |
---|---|---|
listLength | 获取链表长度 | O(1) |
listCreate | 创建一个不包含任何节点的链表 | O(1) |
listRelease | 释放链表及链表中的节点 | O(N) |
listAddNodeHead | 添加节点到表头 | O(1) |
listAddNodeTail | 添加节点到表尾 | O(1) |
listInsertNode | 添加节点到指定节点之前/之后 | O(1) |
listDelNode | 删除节点 | O(1) |
listDup | 复制一个链表 | O(N) |
listSearchKey | 查找并返回等于给定值的节点 | O(N) |