一)简介
双向链表是redis中数据类型List的底层数据结构之一,双向链表也是一种常见的数据结构,在redis中也是大同小异 主要包括三个元素分别为链表,节点,链表迭代器,其设计比较简单 api易懂 为了方便大家阅读 本文做了详细的源码介绍 ,可以收藏下来。
二)性能优化分析
- 节点的双向指针prev和next,方便通过迭代器从头至尾或从尾至头遍历。
- 链表的头尾指针head和tail,对链表的头尾插入,以及头尾节点读取,高效地实现list的一些指令(如push 和pop相关指令)。
- 链表的长度len,得计算链表长度的时间复杂度为O(1),不需要遍历整个链表。
三)来源
以下源码均来自redis 6.0.12版本的 adlist.h和adlist.c文件。
四)数据结构类型定义
/* 双向链表中的节点 */
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;// 链表尾节点
// 定义三个函数指针 为什么要定义这三个函数指针?因为listnode中的数据区域为一个void类型的指针(类似java中泛型),
//所指向的结构可能千差万别,而且这些内存需要手动释放。将常用的这几个函数定义在这里,可以在需要的时候直接回调。
void *(*dup)(void *ptr);//复制节点值
void (*free)(void *ptr);//释放节点值
int (*match)(void *ptr, void *key);///匹配节点值是否相等
unsigned long len; // 链表长度
} list;
五)宏定义
宏名称 | 作用 |
---|---|
listLength | 获取链表的长度值 |
listFirst | 获取链表的首指针 |
listLast | 获取链表的尾指针 |
listPrevNode | 获取当前节点的前驱节点指针 |
listNextNode | 获取当前节点的后继节点指针 |
listNodeValue | 获取当前节点所存储的值 |
listSetDupMethod | 设置链表节点value的复制函数 |
listSetFreeMethod | 设置链表节点value的释放内存函数 |
listSetMatchMethod | 设置链表节点value的比较函数 |
listGetDupMethod | 获取链表节点value的复制函数 |
listGetFree | 获取链表节点value的释放内存函数 |
listGetMatchMethod | 获取链表节点value的比较函数 |
六)API简介
函数 | 参数 | 定义 | 复杂度 |
---|---|---|---|
list *listCreate(void); | 空 | 创建一个list | O(1) |
void listEmpty(list *list); | 要清空元素的链表 | 在不破坏链表本身的情况下,从链表中删除所有元素 | O(N) |
void listRelease(list *list); | 要释放的链表 | 释放链表,清空链表然后释放链表本身的空间 | O(N) |
list *listAddNodeHead(list *list, void *value); | 链表,添加节点的值 | 再链表头部添加一个节点 | O(1) |
list *listAddNodeTail(list *list, void *value); | 链表,添加节点的值 | 再链表尾部添加一个节点 | O(1) |
list *listInsertNode(list *list, listNode *old_node, void *value, int after); | 链表,指定节点,添加节点的值,前或者后 | 在链表的指定节点前或者后添加一个节点 | O(1) |
void listDelNode(list *list,listNode *node); | 链表,指定节点 | 删除指定的节点 | O(1) |
listIter *listGetIterator(list *list, int direction); | 链表,迭代方向 | 生成双向链表的迭代器 | O(1) |
void listReleaseIterator(listIter *iter); | 指定迭代器 | 释放双向链表的迭代器 | O(1) |
listNode *listNext(listIter *iter); | 指定迭代器 | 通过迭代器获取下一个节点 | O(1) |
list *listDup(list *orig); | 要被复制的链表 | 创建指定链表的副本 | O(N) |
listNode *listSearchKey(list *list, void *key); | 链表,指定的值 | 查找与指定key相同值的节点 | O(N) |
listNode *listIndex(list *list, long index); | 链表,索引值 | 根据给定的索引值,返回相应的节点 | O(N) |
void listRewind(list *list, listIter *li); | 链表,迭代器 | 重新初始化迭代器,迭代方向从头至尾 | O(1) |
void listRewindTail(list *list, listIter *li); | 链表,迭代器 | 重新初始化迭代器,迭代方向从尾至头 | O(1) |
void listRotateTailToHead(list *list); | 链表 | 将链表的尾节点移到链表的头节点 | O(1) |
void listRotateHeadToTail(list *list); | 链表 | 将链表的头节点移到链表的尾节点 | O(1) |
void listJoin(list *l,list *o); | 合并链表,被合并链表将两个链表合并o链表合并到l链表后面 | O(1) |
七)API源码分析
listCreate
//创建一个新链表。创建的链表可以使用listRelease()释放,但是每个节点的私有值需要用户在调用listRelease()之前释放,或者使用listSetFreeMethod设置一个free方法。出现错误时,返回NULL。否则指向新列表的指针。
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;
}
//创建一个新链表。
listEmpty
/* 在不破坏链表本身的情况下,从链表中删除所有元素. */
void listEmpty(list *list)
{
unsigned long len;
listNode *current, *next;
current = list->head;
len = list->len;
while(len--) {
next = current->next;
//通过list中的回调函数来释放每一节点数据域的内存空间
if (list->free) list->free(current->value);
zfree(current);//销毁节点空间,zfree是redis定义的空间释放函数,同zmalloc
current = next;
}
//清空后链表只是变成空链表 空间还占着
list->head = list->tail = NULL;
list->len = 0;
}
listRelease
//释放整个链表且这个函数不会失败
//注:和listEmpty不同 清空链表后还把链表本身空间也释放
void listRelease(list *list)
{
listEmpty(list);
zfree(list);
}
listAddNodeHead
//向列表头部添加一个新节点,该节点将指定的'value'指针作为value。
//出错时,返回NULL,不执行任何操作(即列表保持不变)。
//如果成功,将返回传递给函数的'list'指针
list *listAddNodeHead(list *list, void *value)
{
listNode *node;
//主要时分配节点空间时会出错 则返回null
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
//如果链表没节点,就将node作为头和尾节点并把前后指针指向null
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
//如果链表有节点,就将node前指针指向null 后指针指向之前链表的头节点
node->prev = NULL;
node->next = list->head;
//改变之前头节点为当前节点
list->head->prev = node;
list->head = node;
}
list->len++;//长度加1
return list;
}
listAddNodeTail
//原理同listAddNodeHead 就是指针指向相反
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;
}
listInsertNode
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;
}
}
//如果前节点不为空,就把前节点的后节点指向node (确保双向连接)
if (node->prev != NULL) {
node->prev->next = node;
}
//如果后节点不为空,就把后节点的前节点指向node (确保双向连接)
if (node->next != NULL) {
node->next->prev = node;
}
list->len++; //长度加1
return list;
}
listDelNode
//从指定列表中删除指定的节点。
//由调用者释放节点的私有值。
//这个函数不会出错
void listDelNode(list *list, listNode *node)
{
if (node->prev)
node->prev->next = node->next;//node不是头节点,就把node的前节点的后指针指向node的后节点
else
list->head = node->next;//node是头节点,就把node的后节点作为链表的头节点
if (node->next)
node->next->prev = node->prev;//node不是尾节点,就把node的后节点的前指针指向node的前节点
else
list->tail = node->prev;//node是尾节点,就把node的前节点作为链表的尾节点
if (list->free) list->free(node->value);//释放节点值
zfree(node);//释放节点空间
list->len--;//总长度减1
}
listGetIterator
//返回列表迭代器 'iter'。初始化之后,每次调用listNext()都会返回列表的下一个元素。
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;
}
listReleaseIterator
/* 释放指定迭代器的空间 */
void listReleaseIterator(listIter *iter) {
zfree(iter);
}
listNext
//返回迭代器的下一个节点
//可以使用listDelNode()删除当前返回的元素,但不能删除其他元素。
//该函数返回指向列表中下一个元素的指针,如果没有其他元素,则返回NULL
//以下是经典的使用示例:
// iter = listGetIterator(list,<direction>);
// while ((node = listNext(iter)) != NULL) {
// doSomethingWith(listNodeValue(node));
// }
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;
}
listDup
//复制整个链表。内存不足时返回NULL。成功时返回原链表的副本。
//通过listSetDupMethod()函数设置的'Dup'方法用于复制节点值,否则将使用原节点的相同指针值作为复制节点的值。
//不管结果成功还是失败,原来的链表都不会改变
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;
listRewind(orig, &iter);//重置迭代器
//遍历迭代器
while((node = listNext(&iter)) != NULL) {
void *value;
//复制值
if (copy->dup) {
value = copy->dup(node->value);
if (value == NULL) {
listRelease(copy);
return NULL;
}
} else
value = node->value;
//添加尾部节点失败(如空间分配不够失败) 就释放整个链表copy 不再继续遍历下去
if (listAddNodeTail(copy, value) == NULL) {
listRelease(copy);
return NULL;
}
}
return copy;
}
listSearchKey
//在链表中搜索与给定键匹配的节点。
//匹配是使用listSetMatchMethod()设置的'match'方法执行的。
//如果没有设置'match'方法,则每个节点的'value'值将直接与'key'值比较
//成功时返回第一个匹配的节点(搜索从head开始),如果不存在匹配的节点,则返回 NULL。
listNode *listSearchKey(list *list, void *key)
{
listIter iter;
listNode *node;
listRewind(list, &iter);
//遍历迭代器 match上了就返回节点
while((node = listNext(&iter)) != NULL) {
if (list->match) {
if (list->match(node->value, key)) {
return node;
}
} else {
if (key == node->value) {
return node;
}
}
}
return NULL;
}
listIndex
//返回指定的从零开始的索引处的元素,其中0是头。
//使用负整数是为了从尾部开始计数*,-1是最后一个元素,-2是倒数第二个*,以此类推。
//如果索引超出范围将返回NULL
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;
}
listRewind
void listRewind(list *list, listIter *li) {
//把迭代器的后指针和方向设置即可
li->next = list->head;
li->direction = AL_START_HEAD;
}
listRewindTail
//同listRewind 刚好相反
void listRewindTail(list *list, listIter *li) {
li->next = list->tail;
li->direction = AL_START_TAIL;
}
listRotateTailToHead
//旋转链表,移除尾部节点并将其插入头部
void listRotateTailToHead(list *list) {
if (listLength(list) <= 1) return;
//分离当前尾节点 并设置新的尾节点
listNode *tail = list->tail;
list->tail = tail->prev;
list->tail->next = NULL;
//将分离尾节点插入链表头部,做为新的头节点
list->head->prev = tail;
tail->prev = NULL;
tail->next = list->head;
list->head = tail;
}
listRotateHeadToTail
//旋转链表,移除头部节点并将其插入尾部
void listRotateHeadToTail(list *list) {
if (listLength(list) <= 1) return;
listNode *head = list->head;
//分离当前头节点 并设置新的头节点
list->head = head->next;
list->head->prev = NULL;
//将分离头节点插入链表尾部,做为新的尾节点
list->tail->next = head;
head->next = NULL;
head->prev = list->tail;
list->tail = head;
}
listJoin
//将链表'o'中的所有元素添加到链表'l'的末尾
void listJoin(list *l, list *o) {
//将o链表头节点指向链表l的尾节点
if (o->head)
o->head->prev = l->tail;
//将l链表尾节点指向链表o的头节点
if (l->tail)
l->tail->next = o->head;
else
l->head = o->head;
if (o->tail) l->tail = o->tail;
l->len += o->len;
//将o链表设置为空链表
o->head = o->tail = NULL;
o->len = 0;
}