Redis 源码解析之通用双向链表(adlist)

概述

Redis源码中广泛使用
adlist(A generic doubly linked list)
,作为一种通用的双向链表,用于简单的数据集合操作。adlist提供了基本的增删改查能力,并支持用户自定义深拷贝、释放和匹配操作来维护数据集合中的泛化数据
value

adlist 的数据结构

  1. 链表节点
    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;
  1. 链表迭代器
    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
  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 的基本操作

  1. 创建
    :
    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))

image

  1. 在链表首插入新节点
    :
    listAddNodeHead
  • 在空链表插入新节点: 为
    value
    创建新节点,并让
    list

    head

    tail
    都指向新节点。
    image

  • 在非空链表插入新节点:
    (1) 将新节点的
    next
    指向当前首节点(当前首节点将成为第二节点, 将会是新节点的后继节点)
    (2) 将当前节点的
    prev
    指向新节点, 新节点作为新的首节点将成为原首节点的前驱节点。
    (3) 将
    head
    从原本指向旧的首节点改为指向新节点, 将新节点作为链表首。
    (4) 链表总计数加一
    image

/* 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++;
}
  1. 在链表尾插入新节点
    :
    listAddNodeTail
  • 在空链表插入新节点: 逻辑与
    listAddNodeHead
    实现一致。
  • 在非空链表插入新节点:
    (1) 将新节点的
    prev
    指向当前首节点(当前尾节点将成为倒数第二节点, 将会是新节点的前驱节点)
    (2) 将当前节点的
    next
    指向新节点, 新节点作为新的尾节点将成为原尾节点的后继节点。
    (3) 将
    tail
    从原本指向旧的尾节点改为指向新节点, 将新节点作为链表尾。
    (4) 链表总计数加一

image

/* 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++;
}
  1. 在链表指定位置插入
    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) 链表总计数加一

image

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. 删除链表指定节点
    :
    listDelNode
    。 下图以删除中间节点为例,展示了删除的流程。
    (1) 待删除节点的前驱节点的
    next
    指向待删除节点的后继节点;
    (2) 待删除节点的后继节点的
    prev
    指向待删除节点的前驱节点;
    (3) 待删除节点的
    next

    prev
    都置为
    NULL
    ;
    (4) 链表总计数减一
    (5) 如果有注册
    free
    函数,则用
    free
    函数释放待删除节点的
    value
    。然后释放待删除节点。

image


/* 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
链表的信息;

image

/* 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;
}
  1. 其他函数
    : 其他函数实现较为简单,这里简单罗列一下,感兴趣的可以去看下源码。
// 获取 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

关注公众号了解更多 redis 源码细节和其他技术内容。 你的关注是我最大的动力。

image

标签: none

添加新评论