2.Redis内部数据结构-双端链表

Redis 使用双端链表作为列表类型的底层实现之一,也用于构建内部功能,如事务、客户端管理等。双端链表的节点结构包含复制、释放和对比匹配的函数指针,支持高效的头部和尾部操作。此外,Redis 提供了双端链表的迭代器,允许双向迭代。这种数据结构在Redis中的广泛使用提高了操作效率。
摘要由CSDN通过智能技术生成

       链表作为数组之外的一种常用序列抽象, 是大多数高级语言的基本数据类型;

       但是C语言本身不提供链表类型, 大部分C程序都会自己去实现链表类型, Redis也不例外(实现了一个双端链表结构);

双端链表的应用

       双端链表作为一种通用的数据结构, 在Redis内部使用得非常多: 既是Redis列表结构的底层实现之一, 同时为大量Redis 模块所用, 用于构建Redis的其他功能;

实现Redis的列表类型

       双端链表还是 Redis 列表类型的底层实现之一, 当对列表类型的键进行操作 —— 比如执行 RPUSH 、 LPOP 或 LLEN 等命令时, 程序在底层操作的可能就是双端链表;

redis> RPUSH brands Apple Microsoft Google
(integer) 3

redis> LPOP brands
"Apple"

redis> LLEN brands
(integer) 2

redis> LRANGE brands 0 -1
1) "Microsoft"
2) "Google"

Redis 列表使用两种数据结构作为底层实现:1.双端链表;2.压缩列表;

因为双端链表占用的内存比压缩列表要多, 所以当创建新的列表键时, 列表会优先考虑使用压缩列表作为底层实现, 并且在有需要的时候, 才从压缩列表实现转换到双端链表实现;

Redis自身功能的构建

除了实现列表类型以外, 双端链表还被很多 Redis 内部模块所应用:

  • 事务模块使用双端链表依序保存输入的命令;
  • 服务器模块使用双端链表来保存多个客户端;
  • 订阅/发送模块使用双端链表来保存订阅模式的多个客户端;
  • 事件模块使用双端链表来保存时间事件(time event);

类似的应用还有很多, 在后续的章节中我们将看到, 双端链表在 Redis 中发挥着重要的作用。

双端链表的实现

双端链表的实现由 listNode 和 list 两个数据结构构成, 下图展示了由这两个结构组成的一个双端链表实例:

digraph list_and_list_node {    rankdir=LR;    node [shape=record, style = filled, fillcolor = "#95BBE3"];    edge [style = bold];    list_node_1 [label = "<head>listNode |{<prev> prev| value|<next> next}", ];    list_node_2 [label = "<head>listNode |{<prev> prev| value|<next> next}"];    list_node_3 [label = "<head>listNode |{<prev> prev| value|<next> next}"];    list_node_1:next -> list_node_2:head;    list_node_2:next -> list_node_3:head;    list_node_2:prev -> list_node_1:head;    list_node_3:prev -> list_node_2:head;    node [width=1.5, style = filled, fillcolor = "#A8E270"];    list [label = "list |<head> head|<tail> tail|<dup> dup|<free> free|<match> match|<len> len"];    list:tail -> list_node_3:head;    list:head -> list_node_1:head;}

其中, listNode 是双端链表的节点:

typedef struct listNode {
    

    // 前驱节点
    struct listNode *prev;

    // 后继节点
    struct listNode *next;

    // 值
    void *value;

} listNode;

而 list 则是双端链表本身:

typedef struct list {
    

    // 表头指针
    listNode *head;

    // 表尾指针
    listNode *tail;

    // 节点数量
    unsigned long len;

    // 复制函数
    void *(*dup)(void *ptr);
    // 释放函数
    void (*free)(void *ptr);
    // 比对函数
    int (*match)(void *ptr, void *key);
} list;

注意, listNode 的 value 属性的类型是 void * ,说明这个双端链表对节点所保存的值的类型不做限制。

对于不同类型的值,有时候需要不同的函数来处理这些值,因此, list 类型保留了三个函数指针 —— dup 、 free 和 match ,分别用于处理值的复制、释放和对比匹配。在对节点的值进行处理时,如果有给定这些函数,就会调用这些函数。

举个例子:当删除一个 listNode 时,如果包含这个节点的 list 的 list->free 函数不为空,就会先调用删除函数 list->free(listNode->value) 来清空节点的值,再执行余下的删除操作(比如说,释放节点)。

另外,从这两个数据结构的定义上,也可以了解到一些行为和性能特征:

  • listNode 带有 prev 和 next 两个指针,因此,遍历可以双向进行:从表头到表尾,表尾到表头。
  • list 保存了 head 和 tail 两个指针,因此,对链表的表头和表尾进行插入的复杂度都为 θ(1)θ(1) —— 这是高效实现 LPUSH 、 RPOP 、 RPOPLPUSH 等命令的关键。
  • list 带有保存节点数量的 len 属性,所以计算链表长度的复杂度仅为 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值