双端链表简介
双端链表(DoubleLinkedlist),是链表的一种常见数据结构。它是由一系列的节点组成,其中每一个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点。这种结构的存在可以让我们在任意位置插入或删除元素变得非常容易,而不像数组和单向链表一样需要重新排列整个列表。
实现原理
在 Redis 中,双端链表的实现非常精简,每个链表节点只需要三个成员:前置节点、后置节点和一个存储值的指针。这些指针被存储在具有前置节点指针和后置节点指针的结构中。Redis 使用双端链表来实现服务器中的列表、哈希和有序集等格式。
下面我们来看一下 Redis 中链表的实现原理:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
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;
} list;
在这里,我们定义了两个结构体,listNode 是链表节点,而 list 表示整个链表。在 listNode 结构中,prev 和 next 两个成员变量分别代表前置节点和后置节点,而 value 则代表该节点保存的值。在 list 结构体中,head 和 tail 分别代表链表的头和尾,dup、free 和 match 则是 Redis 的三个核心函数,用于在插入、删除、查询等操作中进行值得操作。
实现过程
现在我们已经了解了 Redis 中链表的数据结构,接下来我们将详细了解其中的实现过程。
插入节点
在 Redis 中,我们可以通过 listAddNodeHead 和 listAddNodeTail 来插入一个新节点。
// 头插入
listNode *listAddNodeHead(list *list, void *value) {
listNode *node;
if ((node = malloc(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 node;
}
// 尾插入
listNode *listAddNodeTail(list *list, void *value) {
listNode *node;
if ((node = malloc(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 node;
}
在上述函数中,我们首先通过 malloc 分配空间,再根据需要插入头或尾。如果插入的节点是第一个节点,则将头和尾都赋值为当前节点;否则将前后节点值指定为当前节点,并修改头和尾节点的 prev 或 next 指针。总之,我们首先需要分配足够的空间,然后通过赋值指定信息,最后将节点插入到某个位置即可。
删除节点
删除节点是操作相对复杂的操作,因为要注意处理前后指针,否则就会出现异常。在 Redis 中,可以使用以下两种方法来删除节点。
删除指定节点
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);
free(node);
list->len--;
}
在 Redis 中,我们使用 listDelNode 函数来删除指定节点。在函数中,我们首先判断节点在链表中的位置,并根据不同情况修改节点前后指向的内容。最后释放空间并将节点计数器 -1。
删除尾节点
void listDelNodeTail(list *list) {
listNode *node = list->tail;
if (node->prev)
node->prev->next = NULL;
else
list->head = NULL;
list->tail = node->prev;
if (list->free) list->free(node->value);
free(node);
list->len--;
}
删除节点的该函数与删除指定节点的函数类似,只不过我们是删除指向链表尾部的指针并释放空间。
总结
双端链表作为一种数据结构,其在 Redis 中的实现非常精简,仅需要充分利用指针的特性,就可以实现插入、删除、查询等操作。由于其操作相对于数组和单链表来说十分容易,因此在很多情况下都会是一个不错的选择。双端链表的实现方式受到了很多组件的影响与启发,其设计思路值得我们借鉴学习。