初识链表
常用指令
-
RPUSH
格式:RPUSH 链表键名 一个或多个value
说明:Right Push,依次将zhang3、li4、wang5从右边入队,结果是zhang3靠左,wang5靠右(尾插法,支持批量操作)。
返回:成功插入后列表的元素数量
时间复杂度:O(1)
localhost:1>RPUSH users zhang3 li4 wang5
"3"
-
LRANGE
格式:LRANGE 链表键名 开始下标 结束下标说明:List Range,从左数起,获取列表下标范围是0到2的元素,当结束下标为-1时,代表最后一个元素的下标
返回:指定范围内的元素,从上往下依次对应列表中从左到右的元素
时间复杂度:O(n),慎用
localhost:1>LRANGE users 0 2
1) "zhang3"
2) "li4"
3) "wang5"
# 结束下标为-1,遍历到最后一个元素
localhost:1>LRANGE users 0 -1
1) "zhang3"
2) "li4"
3) "wang5"
-
LPUSH
格式:LPUSH 链表键名 一个或多个value
说明:Left Push将元素li2从左边入队(头插法,支持批量)
返回:成功插入后列表的元素数量
时间复杂度:O(1)
localhost:1>LPUSH users li2
"4"
localhost:1>LRANGE users 0 3
1) "li2"
2) "zhang3"
3) "li4"
4) "wang5"
-
LPOP
格式:LPOP 链表键名
说明:将最左边的元素出队,即li2,列表长度减1
返回:本次指令操作被出队的元素(li2)
时间复杂度:O(1)
localhost:1>LPOP users
"li2"
localhost:1>LRANGE users 0 -1
1) "zhang3"
2) "li4"
3) "wang5"
-
RPOP
格式:RPOP 链表键名
说明:将最左边的元素出队,即wang5,列表长度减1
返回:本次指令操作被出队的元素(wang5)
时间复杂度:O(1)
localhost:1>RPOP users
"wang5"
localhost:1>LRANGE users 0 -1
1) "zhang3"
2) "li4"
其他命令
-
LINDEX
格式:LINDEX 链表键名 元素下标
说明:获取链表中指定下标的元素值
返回:对应下标的元素
时间复杂度:O(n),需要从头开始遍历链表,慎用
localhost:1>lindex users 0
"zhang3"
localhost:1>lindex users 1
"li4"
localhost:1>lindex users 2
null
localhost:1>lindex users -1
"li4"
-
LLEN
格式:LLEN 链表键名
说明:获取链表长度
返回:链表长度(节点数量)
时间复杂度:O(1),直接读取结构体的len属性
localhost:1>llen users
"2"
-
LTRIM
格式:LTRIM 开始下标 结束下标
说明:去掉指定下标范围外的元素,与平时字面意思不同,更应该理解成
LRETAIN
(保留)。当结束下标-开始下标
为负数时,相当于清空链表,慎用。返回:OK则成功
时间复杂度:O(n)
# 把下标为0的zhang3移除
localhost:1>ltrim users 1 1
"OK"
localhost:1>lrange users 0 -1
1) "li4"
结构体
链表节点listNode
结构体
typedef struct listNode {
//前驱节点,指向前一个节点
struct listNode* prev;
//后继结点,指向后一个节点
struct listNodee* next;
//节点值,当前节点的值,因为是void*类型,所以可以存储各种类型的值
void* value;
} listNode;
图解链表节点关系
一条链表是由一个或多个链表节点连接而成的
链表list
为了更方便地管理链表,使用一个list结构来持有链表中常用的链表信息
结构体
typedef struct list {
//头结点,指向链表第一个节点,其prev为NULL
listNode* head;
//尾节点,指向链表最后一个节点,其next为NULL
listNode* tail;
//链表长度,即当前链表节点数量
int len;
//节点值复制函数duplicated
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值对比函数
int (*match)(void *ptr, void *key);
}
图解链表数据结构
特点
- 双端链表:链表有prev和next分别指向上一节点和下一节点,构成了双端链表;
- 无环:Redis中d链的链表是无环的,即头节点的prev不会指向尾节点,尾节点的next不会指向头结点,遍历到NULL时为终点;
- 带头节点和尾节点:访问头、尾节点的时间复杂度为O(1);
- 长度计数器:获取长度的复杂度为O(1),而不需要以O(N)复杂度从头到尾遍历链表;
- 多态:提供了dup、free、match三个函数,可以操作不同类型的节点值。
总结
- 链表被广泛用于实现Redis各种功能,如列表键、发布与订阅、慢查询、监视器等;
- 链表节点由一个listNode结构来表示,每个节点都有指向前一个节点的前驱节点和指向后一个节点的后继结点,构成了双端链表;
- 每个链表都由一个list结构来表示,有表头节点指针,表尾节点指针和链表长度等信息,可以让Redis在部分情况下进行O(1)复杂度的操作;
- 因为list的表头节点head的prev和表尾节点tail的next都指向NULL,所以Redis的链表是一条无环链表;
- 通过为链表设置不同的类型特定函数dup、free和match,Redis的链表可以保存各种不同类型的数据值;
参考
-
《Redis设计与实现》 – 黄健宏
-
《Redis深度历险:核心原理和应用实践》-- 钱文品
-
源码
(1) 链接: https://github.com/huangz1990/redis-3.0-annotated/blob/unstable/src/t_list.c