相关命令:

操作类型

命令

时间复杂度

添加

rpush key value [value ...]

O(k),k 是元素个数,从右侧添加,如果key不存在则会创建这个key,返回key中元素个数

添加

lpush key value [value ...]

O(k),k 是元素个数,从左侧添加,如果key不存在则会创建这个key

添加

rpushx key value [value ...]

O(k),k 是元素个数 ,从右侧添加 ,如果key不存在则不会创建这个key,返回值为零,成功返回key中元素个数。

添加

lpushx key value [value ...]

O(k),k 是元素个数 ,从左侧添加 ,如果key不存在则不会创建这个key,返回值为零,成功返回key中元素个数。

添加

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)

删除

lrem key count value

O(k),k 是元素个数

删除

ltrim key start end

O(k),k 是元素个数

修改

lset key index value

O(n),n 是索引的偏移量

阻塞操作

blpop

O(1)

阻塞操作

brpop

O(1)

redission 操作 list redis中list_阻塞状态

redission 操作 list redis中list_数据库_02

  1. 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,例如要获取图 2-20 的第 5 个元素,可以执行 lindex user:1:messages 4 或者倒数第 1 个元素,lindex user:1:messages -1 就可以得到元素 e,此处的有序指的不是升序或者降序,而是强调顺序很关键,也就是说把一个list的元素不同元素位置颠倒一下得到的新list和原来的不等价,列表中允许出现重复元素。
  2. 区分获取和删除的区别,例如图 2-20 中的 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删除,这个操作会导致列表的⻓度从 5 变成 4;但是执行 lindex 4 只会获取元素,但列表长度是不会变化 的。
  3. 列表中的元素是允许重复的,例如图 2-21 中的列表中是包含了两个 a 元素的。

Redis中list底层更像双端队列,和java中的list并不一样,所不要期待这里的list有和Java中list有相同的方法/狗头!

lpushx & rpushx:

lpushx & rpushx 在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回0:

LPUSHX key element [element ...] // element 存入list的字符串,只能存字符串吗??
RPUSHX key element [element ...] // x 的含义是 exists 存在

redission 操作 list redis中list_list_03

  因为key1不存在,所以 lpush key1 777 888 并没有添加成功,也没有创建key1。

lrange:

lrange key start end // 获取闭区间内的元素

一谈到下标,我们往往就要关注是否越界的问题:

  • C++中,下标越界访问一般认为是“未定义行为”,它会越界的位置访问数据,可能会导致程序崩溃或者得到一个非法数据、或者合法但错误的数据、又或者敲好合法的正确数据都有可能。
  • Java中,如果数组越界会直接抛出异常
  • lrange 命令中如果下标越界采用的是一种软着陆的方式,对于越界的位置会尽可能的获取里面的合法数据,如下图:

redission 操作 list redis中list_数据库_04

虽然下标100越界了,但是redis并没有报错,对于合法区间正常获取元素,对于非法区间不做处理

lrang == list rang != light rang

因此并不存在 rrang

lpop & rpop:

删除元素,redis5中不支持删除指定个数元素,redis6.2及以后版本支持删除指定个数的元素:

lpop key  //redis5
lpop key element//redis6.2

redission 操作 list redis中list_redis_05

lindex:

获取给定下标的元素,同样也是软着陆,需要注意的是这个命令的时间复杂度是O(N)的,N指的该list中的规模。

redission 操作 list redis中list_redis_06

linstert:

linsert key before | after pivot value // O(n),n 是 pivot 距离头尾的距离

 如果有多个基准点,则会从左向右查找第一个基准点添加元素。

lrem:

lrem key count value // lrem => list remove != light rremove 所以没有rrem

 下面是官方解释:如果count是正数代表从head开始删除count个value,如果count是负数代表从tial开始删除count个value,如果count为0代表删除列表中所有的value。

redission 操作 list redis中list_数据库_07

Redis中的list的底层更像是双端列表,而不是Arraylist,所以有些操作的时间复杂度是O(N),关于下标越界的如何处理的问题,lindex key index 如果越界了则会返回nil,并不会报错或者“拆忙盒”,而lset key index element 如果下标越界则会直接报错。

blpop & brpop:

阻塞队列:当队列为空时,尝试出队列会产生阻塞,直到队列不会空;当队列已满时,尝试入队列则会产生阻塞。谁阻塞?想要出队列和入队列的线程阻塞。

blpop & brpop 时出队的命令,所以消费者客户端执行这两个命令的时候队列为空,则会进入阻塞状态(注意可不是redis服务器端阻塞),但是也不能一直阻塞,所以要设置一个过期时间(单位为秒s)解除阻塞状态:

进入阻塞状态:

redission 操作 list redis中list_list_08

当另外一个终端向队列中插入元素后,队列不为空,解除阻塞状态:

redission 操作 list redis中list_阻塞状态_09

 blpop & brpop 还支持监控多个列表:所有列表都为空则进入阻塞状态,那个列表先不空,就从那个列表出元素。如果有多个列表都不为空,则从 blpop key [key ...] timeout 中第一个不为空的列表中出元素。

redission 操作 list redis中list_阻塞状态_10

编码方式:

列表类型的内部编码有两种(现在已经结合为了一种quicklist编码方式) :

  1. ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认 512 个),同时 列表中每个元素的⻓度都⼩于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选用 ziplist 来作为列表的内部编码实现来减少内存消耗。 
  2.  linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis会使用linkedlist作为列表的内部实现。

上述中的512、64是可以在redis配置文件中修改的,这两个配置都已经不用了,原因是list采用了quicklist进行编码:

redission 操作 list redis中list_阻塞状态_11

quicklist相当于ziplist和linkedlist的结合:整体看是一个链表,每个节点又是一个压缩列表,每个压缩列表都不会太大,同时再把多个压缩列表通过链式结构连起来:

redission 操作 list redis中list_数据库_12

应用场景:

充当消息队列:

redission 操作 list redis中list_redission 操作 list_13

上图的消息队列模型可以起到“轮询”的作用,谁先发起brpop谁就先得到元素,如果再发起一brpop,以为redis是单线程模型,所以会重新排到后面。

分频道的消息队列:

redission 操作 list redis中list_redis_14

分页查询:

微博 Timeline 每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不仅是有序的,同时支持按照索引范围获取元素。

1)每篇微博使用哈希结构存储,例如微博中 3 个属性:title、timestamp、content:

比特就业课 hmset mblog:1 title xx timestamp 1476536196 content xxxxx
hmset mblog:n title xx timestamp 1476536196 content xxxxx

2)向用户 Timeline 添加微博,user::mblogs 作为微博的键:

lpush user:1:mblogs mblog:1 mblog:3 ...
lpush user:k:mblogs mblog:9

3)分页获取用户的 Timeline,例如获取用户 1 的前 10 篇微博:

keylist = lrange user:1:mblogs 0 9
for key in keylist {
    hgetall key
}

此方案在实际中可能存在两个问题:

1. 1 + n 问题。即如果每次分页获取的微博个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 pipeline(流水线)模式批量提交命令(把多个命令合并成一个网络请求),或者微博不采用哈希类型,而是使用序列化的字符串类型,使用 mget 获取。

2. 分裂获取文章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。

写的有点乱了,多多包涵!