Redis基本数据结构之list

41 阅读5分钟

1.前言

本文的内容是基于 redis-7.0.0 的源码,具体的源码可以参阅 redis 7.0.0。本文也不会对源文件 adlist.h/adlist.c 中所有的源码进行解释,只会讲解其中的主干部分,如果有兴趣可以参阅 adlist.h 和 adlist.c

2.list 定义

在学数据结构这么门课上,我们都学过双向链表。其实 redis 中的 list 基本数据结构就是双向链表,下面 struct list 的定义如下:

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;

struct listNode 的定义如下:

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

struct list 的 head 和 tail 分别指向链表的第一个结点和最后一个结点。

  • head 指向 list 的第一个结点。
  • tail 指向 list 的最后一个结点。
  • free 是函数指针,list 中的 free 可以设置为对 listNode value 字段进行释放的函数,当进行释放操作时,可以使用设置的 free 函数进行 value 字段的释放操作。
  • dup 是函数指针,list 中的 dup 可以设置为 listNode value 字段进行复制的函数。当进行复制时,会使用设置的 dup 函数进行 value 字段的复制操作。
  • match 是函数指针,list 中的 match 可以设置为 listNode value 字段的比较函数,当进行比较时,会使用设置的 match 函数进行 value 字段的比较操作。
  • len 字段的值为该 list 中 node 的个数,代表 list 中的元素个数。

其内存结构可由下图表示:

redis_list_1.png

从上图可以看出,list 中间的 node 的 prev 和 next 分别指向其前面的 node 和 后面的 node。第一个 node 的 prev 的值为 NULL,最后一个 node 的 next 的值为 NULL。 一个空的 list 其 head 和 tail 的值均为 NULL。

3.迭代器

list 中定义了 struct listIter 来遍历 list。其定义的代码如下:

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

其中 next 表示当前正在访问的元素,而 direction 表示迭代的方向。

/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1

direction 的值为 AL_START_HEAD 表示从头开始向后遍历,AL_START_TAIL 表示从尾向前遍历。

4. 函数

4.1 创建 list

listCreate 函数创建一个 node 数量为 0 的 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;
}

新创建的 list,head 和 tail 的值为 NULL,且 len 的值为 0。

4.2 清空 list

listEmpty 函数释放 list 中的 node。代码如下:

/* Remove all the elements from the list without destroying the list itself. */
void listEmpty(list *list)
{
    unsigned long len;
    listNode *current, *next;

    current = list->head;
    len = list->len;
    while(len--) {
        next = current->next;
        if (list->free) list->free(current->value);
        zfree(current);
        current = next;
    }
    list->head = list->tail = NULL;
    list->len = 0;
}

这时候,需要注意的是,在判断 list->free, 表示是否需要释放 node 中的 value 字段。如果 node->value 有额外的释放内存方法,那么需要调用该方法来释放内存。if (list->free) list->free(current->value); 这一段代码就是判断 list 有没有通过 listSetFreeMethod 来设置 free 函数。

#define listSetFreeMethod(l,m) ((l)->free = (m))

4.3 创建迭代器

listGetIterator 创建一个 list 迭代器。代码如下:

/* Returns a list iterator 'iter'. After the initialization every
 * call to listNext() will return the next element of the list.
 *
 * This function can't fail. */
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;
}

为什么需要迭代器,将遍历 list 操作结构化,如果我们想从前往后遍历一个 list。那么就需要定义一个 listNode * 指向 list 的 head。

listNode *cur = l->head;
while (cur != NULL) {
    do something
    cur = cur->next;
}

而现在我们有了 listIter,就可以写出如下代码:

listIter *iter = listGetIterator(l, AL_START_HEAD)
while (node = liestNext(&iter)) != NULL) {
     do something
}

listNext 将迭代器当前指向的元素返回,并且指向迭代器下一个元素(如果是向前遍历,则是上一个元素)。

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;
}

4.4 复制 list

listDup 函数复制一个 list,代码如下:

/* Duplicate the whole list. On out of memory NULL is returned.
 * On success a copy of the original list is returned.
 *
 * The 'Dup' method set with listSetDupMethod() function is used
 * to copy the node value. Otherwise the same pointer value of
 * the original node is used as value of the copied node.
 *
 * The original list both on success or error is never modified. */
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;
        //如果list 的 dup 函数不为 NULL,则对 node 中的 value 字段进行复制函数调用。
        if (copy->dup) {
            value = copy->dup(node->value);
            if (value == NULL) {
                listRelease(copy);
                return NULL;
            }
        } else {
            value = node->value;
        }
        
        if (listAddNodeTail(copy, value) == NULL) {
            /* Free value if dup succeed but listAddNodeTail failed. */
            if (copy->free) copy->free(value);

            listRelease(copy);
            return NULL;
        }
    }
    return copy;
}

这里需要注意的是:如果 list 的 dup 函数不为 NULL,则对 node 中的 value 字段进行复制函数调用。

4.5

listSearch 函数查找一个 value 字段值为 key 的 node,代码如下:

/* Search the list for a node matching a given key.
 * The match is performed using the 'match' method
 * set with listSetMatchMethod(). If no 'match' method
 * is set, the 'value' pointer of every node is directly
 * compared with the 'key' pointer.
 *
 * On success the first matching node pointer is returned
 * (search starts from head). If no matching node exists
 * NULL is returned. */
listNode *listSearchKey(list *list, void *key)
{
    listIter iter;
    listNode *node;

    listRewind(list, &iter);
    while((node = listNext(&iter)) != NULL) {
        //如果 list match 字段的值不为 NULL,则需要调用 match 指向的函数对 key 和 node 的 value 进行比较。
        if (list->match) {
            if (list->match(node->value, key)) {
                return node;
            }
        } else {
            if (key == node->value) {
                return node;
            }
        }
    }
    return NULL;
}

需要注意的是:如果 list match 字段的值不为 NULL,则需要调用 match 指向的函数对 key 和 node 的 value 进行比较。

5.总结

  • list 类型是一个双向无循环的链表结构。
  • 采用 iter 对 list 中的 node 进行遍历。
  • 可以通过设定free,dup 和 match 的值,针对不同类型的 list 中的 node value 字段进行释放,复制和比较操作。

6.引用

  1. adlist.h
  2. adlist.c