序言
链表在许多的编程语言中都会设计到,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 数据类型实现结构都已经明确说明了,双向链表本身也是常见的数据结构,文章并没有过多赘述。