Redis中的双端链表实现


本文摘自PHP中文网,作者尚,侵删。

adlist作为Redis中的双端链表,在Redis中被广泛的应用到了很多地方,比如 slowlog的存储,主从复制中报错客户端,list数据结构的实现等,很多都与此相关,所以也是非常重要的一个数据结构。

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

(推荐:redis视频教程)

双端链表(以下代码定义在 src/adlist.h):

1

2

3

4

5

6

7

8

9

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):

1

2

3

4

5

typedef struct listNode {

    struct listNode *prev;   //当前节点的上一个节点

    struct listNode *next;   //当前节点的下一个节点

    void *value;     //当前节点存储的值,可以任意类型

} listNode;

1.jpg

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

二、双端链表的简单操作

创建链表(以下代码定义在 src/adlist.c):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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 中的 内存释放函数):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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):

1

2

3

4

5

6

typedef struct listIter {

    listNode *next;    //迭代器指向的下一个节点

    //迭代方向,由于双端链表保存了head和tail的值,所以可以进行两个方向的迭代

    //AL_START_HEAD表示从头向后遍历,AL_START_TAIL表示从尾部向前遍历

    int direction;

} listIter;

迭代器使用实例:

1

2

3

4

5

6

7

8

//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知识请关注redis入门教程栏目。

以上就是Redis中的双端链表实现的详细内容,更多文章请关注木庄网络博客

相关阅读 >>

Redis 6.0版本新特性介绍

Redis集群怎么防止脑裂

浅谈Redis的5种数据类型

Redis怎么保证高可用

Redis事务及相关命令介绍

Redis自定义systemctl管理服务

Redis端口号是什么

Redis缓存失效机制介绍

Redis两种持久化方式的区别是什么

修改Redis ip地址的方法

更多相关阅读请进入《Redis》频道 >>


数据库系统概念 第6版
书籍

数据库系统概念 第6版

机械工业出版社

本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...