Redis基本数据类型--链表

74 阅读3分钟

序言

链表在许多的编程语言中都会设计到,Redis中也常常使用到这种数据结构,其提供了高效的节点重排能力,以及顺序性的节点访问方式。本篇文章主要探讨链表在Redis中的实现方式。

1. 链表和链表节点的实现

在 Redis 中每一个链表节点是使用一个 listNode 结构来表示的:

typedef struct listNode {
    // 前置节点
    struct listNode *prev;
    
    // 后置节点
    struct listNode *next;
    
    //节点的值
    void value;
} listNode;

多个 listNode 节点可以通过 prev 和 next 指针组成双端链表。prve 用来指向上一个节点,next 指向下一个节点。

虽然仅仅使用多个 listNode 结构就可以组成链表,但是 Redis 并没有这样这样,而是使用一个 list 结构来表示链表,操作更加方便。

typedef struct list {
    // 表头节点
    listNode *head;
    
    //表尾节点
    listNode *tail;
    
    // 链表所包含的节点数量
    unsigned long len;
    
    // 节点值复制函数
    void *(*dup)(void *ptr);
    
    // 节点值释放函数
    void *(*free)(void *ptr);
   
    // 节点值对比函数
    void *(*match)(void *ptr);
}

list 结构为链表提供了表头的 head、表尾指针 tail,以及链表的长度计数器 len,而 dup、free、match 成员则是用于实现多态链表所需的类型特定函数:

  • dup 函数用于复制链表节点所保存的值;
  • free 函数用于释放链表节点所保存的值;
  • match 函数则用于对比链表节点所保存的值和另一个输入值是否相等;

Redis 的链表实现的特性可以总结如下:

  • 双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
  • 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
  • 带表头指针和表尾指针:通过Iist结构的head指针和tai1指针,程序获取链表的表头节点和表尾节点的复系度为O(1)。
  • 带链表长度计数器:程序使用1ist结构的1en属性来对1ist持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
  • 多态:链表节点使用void*指针来保存节点值,并且可以通过1ist结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

2. list数据结构常用命令

命令描述示例
LPUSH key value [value...]将一个或多个值插入到列表头部LPUSH mylist "world" "hello",将"hello"和"world"依次插入到名为mylist的列表头部
RPUSH key value [value...]将一个或多个值插入到列表尾部RPUSH mylist "again",将"again"插入到mylist的尾部
LPOP key移除并返回列表的第一个元素LPOP mylist,返回并移除mylist的第一个元素
RPOP key移除并返回列表的最后一个元素RPOP mylist,返回并移除mylist的最后一个元素
LREM key count value根据count的值移除列表中与value相等的元素对于列表["a", "b", "c", "a", "b"],LREM mylist 2 "b",从表头开始移除2个"b"
LSET key index value将列表key中指定索引index的元素的值设置为value对于列表["a", "b", "c"],LSET mylist 1 "d",将索引为1的元素改为"d"
LINDEX key index返回列表key中指定索引index的元素LINDEX mylist 0,返回mylist中索引为0的元素

总结

这篇文章相对来说篇幅较小,但是其中 Redis 的 list 数据类型实现结构都已经明确说明了,双向链表本身也是常见的数据结构,文章并没有过多赘述。