Redis 2.8.9源码 - Redis中的双端链表实现 adlist

本文为做者原创,转载请注明出处:http://my.oschina.net/fuckphp/blog/269801php

adlist做为Redis中的双端链表,在Redis中被普遍的应用到了不少地方,好比 slowlog的存储,主从复制中报错客户端,list数据结构的实现等,不少都与此相关,因此也是很是重要的一个数据结构。node

Redis 对双端链表的描述和实现源码在 src/adlist.h   src/adlist.c,关于学习Redis双端链表如何进行测试或debug,请参考另一篇文章:Redis 2.8.9源码 - 双向链表操做函数头整理,并注释做用和参数说明(附测试方法和代码)redis


一)、Redis中双端链表的数据结构数据结构

双端链表(如下代码定义在 src/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;

双端链表的节点(如下代码定义在 src/adlist.h)学习

typedef struct listNode {
    struct listNode *prev;   //当前节点的上一个节点
    struct listNode *next;   //当前节点的下一个节点
    void *value;     //当前节点存储的值,能够任意类型
} listNode;

引用一张来自《Redis 设计与实现 初版》 的一张图测试

list 经过 head 和 tail两个指针分别指向链表的头尾两端,使得遍历链表能够从正反两个顺序进行遍历,而 listNode 的void *value,则能够保存任意数据,并能够经过dup,free,match来自定义如何处理listNode的值。spa

相关例子请看另外一篇文章: 双向链表操做函数头整理,并注释做用和参数说明(附测试方法和代码) 中的测试代码中的实例..net

2、双端链表的简单操做
debug

建立链表(如下代码定义在 src/adlist.c)

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

追加到链表结尾(如下代码定义在 src/adlist.c)

list *listAddNodeTail(list *list, void *value)
{
    listNode *node;    //初始化node节点

    //为新的node节点分配内存
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    //为node节点设置值
    node->value = value;
    
    if (list->len == 0) {
        //若是空链表则 将头尾指向 新节点 并后继前驱节点设置为NULL
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        //不然将节点的前驱节点设置为原来的尾部节点
        node->prev = list->tail;
        //因为追加到尾部,后继节点为NULL
        node->next = NULL;
        //以前的尾部节点的后继节点设置为新增的节点
        list->tail->next = node;
        //将列表的尾部节点指针指向新增的节点
        list->tail = node;
    }
    //增长链表长度
    list->len++;
    return list;
}

遍历链表:

经常使用的遍历链表有两种方式,一个是根据链表长度经过while循环去手动遍历,还有一种是用Redis双端链表提供的迭代器来遍历。

手动遍历(如下代码定义在 src/adlist.c 中的 内存释放函数)

void listRelease(list *list)
{
    unsigned long len;
    //current表示当前遍历的游标指针,next表示下次遍历的游标指针(next做为临时变量用于保存下一个节点)
    listNode *current, *next;
    //将current指向头部节点
    current = list->head;
    //计算长度(其实就是 listLength(list))
    len = list->len;
    //经过长度递减的方式进行遍历,便利到长度为0的时候,遍历结束
    while(len--) {
        //保存下次循环的节点指针
        next = current->next;
        //释放掉当前节点,若是设置释放节点的回调函数,则执行用户自定义的函数
        if (list->free) list->free(current->value);
        zfree(current);
        //将游标指向下个节点
        current = next;
    }
    zfree(list);
}

迭代器方式遍历请见下文。

三)、双端链表的迭代器

Redis 为了方便对链表的迭代,对链表进行了迭代器的封装,迭代器结构以下(如下代码定义在 src/adlist.h):

typedef struct listIter {
    listNode *next;    //迭代器指向的下一个节点
    //迭代方向,因为双端链表保存了head和tail的值,因此能够进行两个方向的迭代
    //AL_START_HEAD表示从头向后遍历,AL_START_TAIL表示从尾部向前遍历
    int direction;
} listIter;

 迭代器使用实例:

//l表示list结构,n表示listNode结构,listNode的值保存的是sds字符串
//建立迭代器对象
iter = listGetIterator(l, AL_START_HEAD);
//循环获取下一个须要遍历的节点
while ((n = listNext(iter))) {
    //输出返回的节点n的value值
    printf("Node Value: %s\n", listNodeValue(n));
}


更多实例和Redis为链表提供的Api,请参考另一篇文章:

Redis 2.8.9源码 - 双向链表操做函数头整理,并注释做用和参数说明(附测试方法和代码)




参考资料:

Redis2.8.9源码   src/adlist.h   src/adlist.c

Redis 设计与实现(初版)

相关文章
相关标签/搜索