Redis学习(二)链表(list)

9 篇文章 0 订阅
8 篇文章 1 订阅

1. 什么是链表

链表作为一种常用数据结构,链表内置在很多高级的编程语言里面,以为 Redis 使用的 C语言并没有内置这种数据结构,所以 Redis 构建了自己的链表实现。
在Redis 中,链表的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis 就会使用链表作为列表建的底层实现。

2. 链表的数据结构

2.1 链表节点

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

多个链表节点就可以组成双向链表了,如图所示:
在这里插入图片描述

2.2 链表

Redis 在 listNode 基础上,加多了一个 list。使用 list 来操作链表的话,会更方便。具体的好处可以参考后面 list & listNode API。

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

list 结构为链表提供了表头指针 head、表尾指针 tail、以及链表长度计数器 len,这些都不难理解。而 dupfreematch 成员变量则是用于实现多态链表所需的类型特定函数:

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

list 结构如图所示:
在这里插入图片描述

3. 链表基本使用

3.1 LLEN

Redis Llen 命令用于返回列表的长度。 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误。

3.1.1 语法

redis 127.0.0.1:6379> LLEN KEY_NAME 

3.1.2 示例

# 读取不存在的key的链表长度
127.0.0.1:6379> EXISTS myList
(integer) 0
127.0.0.1:6379> LLEN myList
(integer) 0

# 读取存在的key的链表长度
127.0.0.1:6379> LPUSH myList value1
(integer) 1
127.0.0.1:6379> LLEN myList
(integer) 1

# 读取非链表key
127.0.0.1:6379> EXISTS myKey
(integer) 1
127.0.0.1:6379> LLEN myKey
(error) WRONGTYPE Operation against a key holding the wrong kind of value

3.2 LPUSH & RPUSH

两个命令都是往链表里面插入值,如果 key 不存在,一个空列表会被创建并执行插入操作。 当 key 存在但不是列表类型时,返回一个错误。
不同的是,LPUSH 是往头部插入值,RPUSH 是往尾部插入值。

3.2.1 语法

redis 127.0.0.1:6379> LPUSH KEY_NAME VALUE1.. VALUEN
redis 127.0.0.1:6379> RPUSH KEY_NAME VALUE1..VALUEN

3.2.2 示例

# myList 是个空链表
127.0.0.1:6379> LLEN myList
(integer) 0

# 从头部分别插入:5 4 3 2 1
127.0.0.1:6379> LPUSH myList 5 4 3 2 1
(integer) 5
127.0.0.1:6379> LLEN myList
(integer) 5
127.0.0.1:6379> LRANGE myList 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

# 从头部分别插入:6 7 8 9 10
127.0.0.1:6379> RPUSH myList 6 7 8 9 10
(integer) 10

# 得到的结果
127.0.0.1:6379> LRANGE myList 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"

3.3 LPUSHX & RPUSHX

具体作用和 LPUSH & RPUSH 类似,唯一区别是,如果目标链表不存在,则操作无效。

3.3.1 语法

redis 127.0.0.1:6379> LPUSHX KEY_NAME VALUE1.. VALUEN
redis 127.0.0.1:6379> RPUSHX KEY_NAME VALUE1..VALUEN

3.3.2 示例

127.0.0.1:6379> EXISTS myList2
(integer) 0
127.0.0.1:6379> LPUSHX myList2 value1
(integer) 0
127.0.0.1:6379> LLEN myList2
(integer) 0

3.4 LRANGE

Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

3.4.1 语法

redis 127.0.0.1:6379> LRANGE KEY_NAME START END

3.4.2 示例

# 查询已存在的链表
127.0.0.1:6379> LRANGE myList 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"

# 查询不存在的链表
127.0.0.1:6379> LRANGE myList3 0 -1
(empty array)

# 查询非链表
127.0.0.1:6379> LRANGE myKey 0 -1
(error) WRONGTYPE Operation against a key holding the wrong kind of value

3.5 LINDEX

Redis Lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

3.5.1 语法

redis 127.0.0.1:6379> LINDEX KEY_NAME INDEX_POSITION 

3.5.2 示例

127.0.0.1:6379> EXISTS myList
(integer) 1
127.0.0.1:6379> LRANGE myList 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"

# 获取链表范围内的下标值
127.0.0.1:6379> LINDEX myList 5
"6"

# 获取链表范围外的下标值
127.0.0.1:6379> LINDEX myList 11
(nil)

3.5 LPOP & RPOP

返回列表中的元素。 当列表 key 不存在时,返回 nil 。
LPOP 返回列表中的头部元素
RPOP 返回列表中的尾部元素

3.5.1 语法

redis 127.0.0.1:6379> Lpop KEY_NAME 
redis 127.0.0.1:6379> Rpop KEY_NAME 

3.5.2 示例

127.0.0.1:6379> LRANGE myList 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"
127.0.0.1:6379> LPOP myList
"1"
127.0.0.1:6379> RPOP myList
"10"
127.0.0.1:6379> LRANGE myList 0 -1
1) "2"
2) "3"
3) "4"
4) "5"
5) "6"
6) "7"
7) "8"
8) "9"

3.6 LREM

Redis Lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
COUNT 的值可以是以下几种:

  • count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
  • count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
  • count = 0 : 移除表中所有与 VALUE 相等的值。

3.6.1 语法

redis 127.0.0.1:6379> LREM key count VALUE

3.6.2 示例

127.0.0.1:6379> RPUSH list2 "hello" "hello" "hello" "redis" "hello" "hello" "hello"
(integer) 7
127.0.0.1:6379> LRANGE list2 0 -1
1) "hello"
2) "hello"
3) "hello"
4) "redis"
5) "hello"
6) "hello"
7) "hello"

# count > 0,从头部开始删除一个"hello"127.0.0.1:6379> LREM list2 1 "hello"
(integer) 1
127.0.0.1:6379> LRANGE list2 0 -1
1) "hello"
2) "hello"
3) "redis"
4) "hello"
5) "hello"
6) "hello"


# count < 0,从尾部开始删除一个"hello"127.0.0.1:6379> LREM list2 -1 "hello"
(integer) 1
127.0.0.1:6379> LRANGE list2 0 -1
1) "hello"
2) "hello"
3) "redis"
4) "hello"
5) "hello"

# count = 0,删除所有"hello"127.0.0.1:6379> LREM list2 0 "hello"
(integer) 4
127.0.0.1:6379> LRANGE list2 0 -1
1) "redis"

3.7 BLPOP & BRPOP

BLPOP & BRPOP的作用和 LPOP & RPOP 类似,区别在于,如果链表为空时,BLPOP & BRPOP阻塞列表直到等待超时或发现可弹出元素为止。而 LPOP & RPOP 是返回 nil

3.7.1 语法

redis 127.0.0.1:6379> BLPOP LIST1 LIST2 .. LISTN TIMEOUT
redis 127.0.0.1:6379> BRPOP LIST1 LIST2 .. LISTN TIMEOUT 

3.7.2 示例

127.0.0.1:6379> RPUSH list1 a b c
(integer) 3
127.0.0.1:6379> BRPOP list1 0
1) "list1"
2) "c"
127.0.0.1:6379> BRPOP list1 0
1) "list1"
2) "b"
127.0.0.1:6379> BRPOP list1 0
1) "list1"
2) "a"
127.0.0.1:6379> BRPOP list1 5
(nil)
(5.06s)

4. 链表 API

函数作用时间复杂度
listSearchKey查找并返回链表中包含给定值的节点O(N),N 为链表长度
listIndex返回链表在给定索引上的节点O(N),N 为链表长度
listDelNode从链表中删除给定节点O(N),N 为链表长度
listDup复制一个给定链表的副本O(N),N 为链表长度
listRelease释放给定链表,以及链表中的所有节点O(N),N 为链表长度

这里只给出了时间复杂度为:O(N) 的 API,其余的操作时间复杂度皆为:O(1)。在日常操作中,应该更关注链表中那些操作的时间复杂度较高,可能会影响整体性能。

5. 参考

6. 其他相关文章

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值