list相当于链表、数据表
1.list类型基本介绍
- 列表中的元素是有序的
- "有序"的含义,要根据上下文区分~~
- 有的时候,谈到有序,指的是"升序","降序”
- 有的时候,谈到的有序,指的是, 顺序很关键~~
- 如果把元素位置颠倒,顺序调换.
- 此时得到的新的 List 和之前的 List 是不等价的!!
- 同样一个词,怎么理解,务必要结合上下文,结合具体场景~~
- 栈/堆.(数据结构的,操作系统的,M 的)
- 同步(同步和互斥的同步,还是同步和异步的同步)
1.区分获取和删除的区别
lindex 能获取到元素的值
lrem 也能返回被删除元素的值.
2.列表中的元素是允许重复的
像 hash 这样的类型, field 是不能重复的
因为当前的 List,头和尾都能高效的插入删除元素, 就可以把这个 List 当做一个 栈/队列 来使用了
Redis 有一个典型的应用场景,就是作为消息队列
最早的时候, 就是通过 List 类型~.
后来 Redis 又提供了 stream 类 (用于消息队列)
2.list相关命令
2.1 LPUSH
将⼀个或者多个元素从左侧放⼊(头插)到 list 中。
语法:
LPUSH key element [element ...]
命令有效版本:1.0.0 之后时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.返回值:插⼊后 list 的长度。
⽰例:
redis> LPUSH mylist "world"(integer) 1redis> LPUSH mylist "hello"(integer) 2redis> LRANGE mylist 0 -11) "hello"2) "world"//按照顺序,依次头插这几个元素.
//全都插入完毕,4 是在最前面的!!!//如果 key 已经存在, 并且 key 对应的 value 类型,不是 list//此时 lpush 命令就要报错.//此处的序号与下标无关,序号只是标识项,描述一下顺序
2.2 LPUSHX
在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回
语法:
LPUSHX key element [element ...]
命令有效版本:2.0.0 之后时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.返回值:插⼊后 list 的⻓度。
示例:
redis> LPUSH mylist "World"(integer) 1redis> LPUSHX mylist "Hello"(integer) 2redis> LPUSHX myotherlist "Hello"(integer) 0redis> LRANGE mylist 0 -11) "Hello"2) "World"redis> LRANGE myotherlist 0 -1(empty array)
2.3 RPUSH
将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。
语法:
RPUSH key element [element ...]
命令有效版本:1.0.0 之后时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.返回值:插⼊后 list 的⻓度。
⽰例:
redis> RPUSH mylist "world"(integer) 1redis> RPUSH mylist "hello"(integer) 2redis> LRANGE mylist 0 -11) "world"2) "hello"
2.4 RPUSHX
在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。
语法:
RPUSHX key element [element ...]
命令有效版本:2.0.0 之后时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.返回值:插⼊后 list 的⻓度。
⽰例:
redis> RPUSH mylist "World"(integer) 1redis> RPUSHX mylist "Hello"(integer) 2redis> RPUSHX myotherlist "Hello"(integer) 0redis> LRANGE mylist 0 -11) "World"2) "Hello"redis> LRANGE myotherlist 0 -1(empty array)
2.5 LRANGE
获取从 start 到 end 区间的所有元素,左闭右闭。
语法:
//此处的l不是left,而是list
LRANGE key start stop
命令有效版本:1.0.0 之后时间复杂度:O(N)返回值:指定区间的元素。
⽰例:
redis> RPUSH mylist "one"(integer) 1redis> RPUSH mylist "two"(integer) 2redis> RPUSH mylist "three"(integer) 3redis> LRANGE mylist 0 01) "one"redis> LRANGE mylist -3 21) "one"2) "two"3) "three"redis> LRANGE mylist -100 1001) "one"2) "two"3) "three"redis> LRANGE mylist 5 10(empty array)
2.6 LPOP
从 list 左侧取出元素(即头删)。
语法:
LPOP key
命令有效版本:1.0.0 之后时间复杂度:O(1)返回值:取出的元素或者 nil。
⽰例:
redis> RPUSH mylist "one" "two" "three" "four" "five"
(integer) 5
redis> LPOP mylist
"one"
redis> LPOP mylist
"two"
redis> LPOP mylist
"three"
redis> LRANGE mylist 0 -1
1) "four"
2) "five"
2.7 RPOP
从 list 右侧取出元素(即尾删)。
语法:
RPOP key我们所用的版本是5 不考虑
命令有效版本:1.0.0 之后时间复杂度:O(1)返回值:取出的元素或者 nil。
⽰例:
redis> RPUSH mylist "one" "two" "three" "four" "five" (integer) 5 redis> RPOP mylist "five" redis> LRANGE mylist 0 -1 1) "one" 2) "two" 3) "three" 4) "four"
Redis 中的 list 是一个双端队列~~
从两头插入/删除元素都是非常高效 O(1)
搭配使用 rpush 和 lpop, 就相当于队列了搭配使用 rpush 和 rpop, 就相当于栈了,
2.8 LINDEX
获取从左数第 index 位置的元素。
语法:
LINDEX key index
命令有效版本:1.0.0 之后时间复杂度:O(N),此处的N是list中元素的个数返回值:取出的元素或者 nil。
⽰例:
redis> LPUSH mylist "World"
(integer) 1
redis> LPUSH mylist "Hello"
(integer) 2
redis> LINDEX mylist 0
"Hello"
redis> LINDEX mylist -1
"World"
redis> LINDEX mylist 3
(nil)
2.9 LINSERT
在特定位置插⼊元素。
语法:
LINSERT key <BEFORE | AFTER> pivot elementpivot 以该元素为基准
命令有效版本:2.2.0 之后时间复杂度:O(N)返回值:插⼊后的 list ⻓度。
⽰例:
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
//基准不是下标,而是元素
万一要插入的列表中,基准值,存在多个,咋办?
linsert 进行插入的时候,要根据基准值, 找到对应的位置,从左往右找,找到第一个符合基准值的位置即可.
O(N),N 表示列表的长度
2.10 LLEN
获取 list ⻓度。
语法:
LLEN key
命令有效版本:1.0.0 之后时间复杂度:O(1)返回值:list 的⻓度。
⽰例:
redis> LPUSH mylist "World"(integer) 1redis> LPUSH mylist "Hello"(integer) 2redis> LLEN mylist(integer) 2
2.11 LREM
count>0,从左往右找
count<0,从右往左找
count=0,全部删除
2.12 LRIM
只保留区间内的数
时间复杂度是O(N),N是当前要删除的元素的个数
2.13 LSET
3.阻塞版命令
- redis 中的 list 也相当于 阻塞队列 一样
- 线程安全是通过单线程模型支持的.
- 阻塞,则只支持"队列为空"的情况,不考虑"队列满”
- 但阻塞版本会根据 timeout,阻塞一段时间,期间 Redis 可以执行其他命令使用 brpop 和 blpop 的时候,这里是可以显式设置阻塞时间的!!!(不一定是无休止的等待!!)【此处的 blpop 和 brpop 看起来好像耗时很久,但是实际上并不会对 redis 服务器产生负面影响!!】
- 命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。
blpop 和 brpop 都是可以同时去尝试获取多个 key 的列表的元素的~~
多个 key 对应多个 list这多个 |ist 哪个有元素了,就会返回哪个元素,
如果多个客户端同时多一个键执行 pop,则最先执行命令的客户端会得到弹出的元素。- 区别
blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,和对应⾮阻塞版本的作⽤基本⼀致,除了:
•
在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的。但如果列表中没有元素,⾮阻塞版本会理
解返回 nil,但阻塞版本会根据 timeout,阻塞⼀段时间,期间 Redis 可以执⾏其他命令,但要求执
⾏该命令的客⼾端会表现为阻塞状态(如图 2-22 所⽰)。
•
命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元
素,命令⽴即返回。
•
如果多个客⼾端同时多⼀个键执⾏ pop,则最先执⾏命令的客⼾端会得到弹出的元素。
3.1 BLPOP
LPOP 的阻塞版本。
语法:
BLPOP key [key ...] timeout
- 此处 可以指定一个 key 或者 多个 key
- 每个 key 都对应一个 list.
- 如果这些 list 有任何一个非空,blpop 都能够把这里的元素给获取到. 立即返回如果这些 list 都为空, 此时就需要阻塞等待, 等待其他客户端往这些 list 中插入元素了
- 单位是 秒(Redis 6, 超时时间允许设定成小数.Redis 5 中,超时时间,得是整数)
命令有效版本:1.0.0 之后时间复杂度:O(1)返回值:取出的元素或者 nil。
⽰例:
redis> EXISTS list1 list2(integer) 0redis> RPUSH list1 a b c(integer) 3redis> BLPOP list1 list2 01) "list1"2) "a"当插入任何一个元素都会返回
3.2 BRPOP
RPOP 的阻塞版本。
语法:
BRPOP key [key ...] timeout
命令有效版本:1.0.0 之后时间复杂度:O(1)返回值:取出的元素或者 nil。
⽰例:
redis> DEL list1 list2(integer) 0redis> RPUSH list1 a b c(integer) 3redis> BRPOP list1 list2 01) "list1"2) "c"
4.命令小结
操作类型 | 命令 | 时间复杂度 |
添加 | rpush key value [value ...] | O(k),k 是元素个数 |
lpush key value [value ...] | O(k),k 是元素个数 | |
linsert key before | after pivot value | O(n),n 是 pivot 距离头尾的距离 | |
查找 | lrange key start end |
O(s+n),s 是 start 偏移量,n 是 start 到 end 的范围
|
lindex key index | O(n),n 是索引的偏移量 | |
llen key | O(1) | |
删除 | lpop key | O(1) |
rpop key | O(1) | |
lremkey count value | O(k),k 是元素个数 | |
ltrim key start end | O(k),k 是元素个数 | |
修改 | lset key index value | O(n),n 是索引的偏移量 |
阻塞操作 | blpop brpop | O(1) |
5.内部编码
列表类型的内部编码有两种:
•
ziplist(压缩列表):当列表的元素个数⼩于 list-max-ziplist-entries 配置(默认 512 个),同时
列表中每个元素的⻓度都⼩于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选⽤
ziplist 来作为列表的内部编码实现来减少内存消耗。
•
linkedlist(链表):当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内
部实现。
上述方式为老版本的内部编码,现在用的是quicklistquicklist 相当于是 链表 和 压缩列表 的结合整体还是一个链表,链表的每个节点,是一个压缩列表。
每个压缩列表,都不让它太大同时再把多个压缩列表通过链式结构连起来~~
6.list的应用场景
6.1 存储多个数据
6.2 消息队列(生产者消费模型)(少见)
Redis 可以使⽤ lpush + brpop 命令组合实现经典的阻塞式⽣产者-消费者模型队列,⽣产者客⼾端使⽤ lpush 从列表左侧插⼊元素,多个消费者客⼾端使⽤ brpop 命令阻塞式地从队列中"争抢" 队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性。
- 谁先执行的这个 brpop 命令
- 谁就能拿到这个新来的元素
- 像这样的设定,就能构成一个"轮询"式的效果.
- 假设消费者执行顺序是123当新元素到达之后,首先是消费者1 拿到元素.(按照执行brpop 命令的先后顺序来决定谁获取到的)
- 消费者1 拿到元素之后,也就从 brpop 中返回了(相当于这个命令就执行完了)
- 如果消费者1 还想继续消费,就需要重新执行 brpop.
- 此时, 再来一个新的元素过来, 就是消费者2 拿到该元素也从 brpop 中返回~~ 如果消费者2 还想继续消费也需要重新执行 brpop.
- 再来一个新元素,就是消费者3 拿到这个元素了
分频道的消息队列
6.3 微博 Timeline
每个⽤⼾都有属于⾃⼰的 Timeline(微博列表),现需要分⻚展⽰⽂章列表。此时可以考虑使⽤
列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素。