List 列表list相当于是数组 / 顺序表但是内部编码并不是而是更和双端队列配对列表类型是用来存储多个有序的字符串如下图所示a、b、c、d、e五个元素从左到右组成了一个有序的列表列表中的每个字符串称为元素element一个列表最多可以存储2³² - 1个元素。在Redis中可以对列表两端插入push和弹出pop还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构它可以充当栈和队列的角色在实际开发上有很多应用场景。列表两端插入和弹出操作一样的这里的列表一样是针对value而言的毕竟Redis中的Key永远都是StringValue可以是任意的类型Redis的下标支持负数下标我们之前的hash就已经说过了列表的获取、删除等操作列表类型的特点第一、列表中的元素是有序的这意味着可以通过索引下标获取某个元素或者某个范围的元素列表。例如要获取上图的第 5 个元素可以执行lindex user:1:messages 4或者获取倒数第 1 个元素执行lindex user:1:messages -1就可以得到元素e。注意有序 的含义需要根据上下文进行区分有的时候谈到有序是指升序、降序有的时候谈到有序是指插入顺序固定、位置很关键此时对应的就是list结构也就意味着只要把元素位置颠倒、顺序调换得到的新list和之前的list就不再等价。【这不就是看成站和队列了嘛】第二、区分获取和删除的区别。例如上图中的lrem 1 b是从列表中把从左数遇到的前 1 个b元素删除这个操作会导致列表长度从 5 变成 4但是执行lindex 4只是单纯获取元素列表长度不会发生任何变化。第三、列表中的元素允许重复但像hash这种结构field字段是不能重复的。例如列表中可以同时包含两个a元素。列表中允许有重复元素命令LPUSH将一个或者多个元素从左侧放入头插到list中。语法LPUSH key element [element ...]命令有效版本1.0.0 之后时间复杂度只插入一个元素为 O (1)插入多个元素为 O (N)N 为插入元素个数。返回值插入后list的长度。示例127.0.0.1:6379 lpush mylist 1 2 3 4 (integer) 4 127.0.0.1:6379 lpush mylist 5 6 7 8 (integer) 8 127.0.0.1:6379 lpush mylist 9 9 (integer) 10注意如果Key已经存在并且Key对应的Value类型不是list此时lpush命令就会报错Redis中的所有的这些各种数据结构的操作都是类似的效果LPUSHX在key存在时将一个或者多个元素从 左侧放入头插到list中。不存在直接返回。语法LPUSHX key element [element ...]命令有效版本2.0.0 之后时间复杂度只插入一个元素为 O (1)插入多个元素为 O (N)N 为插入元素个数。返回值插入后list的长度。示例127.0.0.1:6379 lpushx key 10 11 12 (integer) 0 127.0.0.1:6379 lpushx mylist 10 11 12 (integer) 16 127.0.0.1:6379 lrange mylist 0 -1 1) 12 2) 11 3) 10 4) 12 5) 11 6) 10 7) 9 8) 9 9) 8 10) 7 11) 6 12) 5 13) 4 14) 3 15) 2 16) 1RPUSH将一个或者多个元素从右侧放入尾插到list中。语法RPUSH key element [element ...]命令有效版本1.0.0 之后时间复杂度只插入一个元素为 O (1)插入多个元素为 O (N)N 为插入元素个数。返回值插入后list的长度。示例127.0.0.1:6379 rpush mylist 0 -1 -2 (integer) 19 127.0.0.1:6379 lrange mylist 0 -1 1) 12 2) 11 3) 10 4) 12 5) 11 6) 10 7) 9 8) 9 9) 8 10) 7 11) 6 12) 5 13) 4 14) 3 15) 2 16) 1 17) 0 18) -1 19) -2RPUSHX在key存在时将一个或者多个元素从右侧放入尾插到list中。语法RPUSHX key element [element ...]命令有效版本2.0.0 之后时间复杂度只插入一个元素为 O (1)插入多个元素为 O (N)N 为插入元素个数。返回值插入后list的长度。示例127.0.0.1:6379 rpushx key -3 -4 (integer) 0 127.0.0.1:6379 rpushx mylist -3 -4 (integer) 21 127.0.0.1:6379 lrange mylist 0 -1 1) 12 2) 11 3) 10 4) 12 5) 11 6) 10 7) 9 8) 9 9) 8 10) 7 11) 6 12) 5 13) 4 14) 3 15) 2 16) 1 17) 0 18) -1 19) -2 20) -3 21) -4LRANGELRANGE list range ! left range, 是获取从start到end区间的所有元素左闭右闭。语法LRANGE key start stop命令有效版本1.0.0 之后时间复杂度O (N)返回值指定区间的元素。示例127.0.0.1:6379 lrange mylist 0 9 1) 9 2) 9 3) 8 4) 7 5) 6 6) 5 7) 4 8) 3 9) 2 10) 1 127.0.0.1:6379 lrange mylist -1 -2 (empty array) 127.0.0.1:6379 lrange mylist -2 -1 1) 2 2) 1 127.0.0.1:6379 lrange mylist 0 -1 1) 9 2) 9 3) 8 4) 7 5) 6 6) 5 7) 4 8) 3 9) 2 10) 1注意下标超出范围也就是越界是没事的不像 C 越界就是未定义行为而是尽可能给出此处对于越界下标的处理方式更接近于Python的处理方式 ——Python的切片LPOP从list左侧取出元素即头删。语法LPOP key命令有效版本1.0.0 之后时间复杂度O (1)返回值取出的元素或者nil。示例127.0.0.1:6379 lrange mylist 0 -1 1) 12 2) 11 3) 10 4) 12 5) 11 6) 10 7) 9 8) 9 9) 8 10) 7 11) 6 12) 5 13) 4 14) 3 15) 2 16) 1 17) 0 18) -1 19) -2 20) -3 21) -4 127.0.0.1:6379 lpop mylist 12 127.0.0.1:6379 lpop mylis (nil)RPOP从list右侧取出元素即尾删。语法RPOP key [count]命令有效版本1.0.0 之后时间复杂度O (1)返回值取出的元素或者nil。LPOP和RPOP在当前的Redis 5版本中都是没有count参数的从Redis 6.2版本新增了一个count参数这里的count就表示要头删尾删的个数示例127.0.0.1:6379 rpop mylist -4 127.0.0.1:6379 rpop mylist -3 127.0.0.1:6379 rpop mylis (nil)LINDEX获取从左数第index位置的元素。语法LINDEX key index命令有效版本1.0.0 之后时间复杂度O (N)返回值取出的元素或者nil。示例127.0.0.1:6379 lrange mylist 0 -1 1) 11 2) 10 3) 12 4) 11 5) 10 6) 9 7) 9 8) 8 9) 7 10) 6 11) 5 12) 4 13) 3 14) 2 15) 1 16) 0 17) -1 18) -2 127.0.0.1:6379 lindex mylist 0 11 127.0.0.1:6379 lindex mylist 134 (nil) 127.0.0.1:6379 lindex mylist 12 3 127.0.0.1:6379 lindex mylis 12 (nil)LINSERT在特定位置插入元素。语法LINSERT key BEFORE | AFTER pivot element命令有效版本2.2.0 之后时间复杂度O (N)返回值插入后的list长度。示例127.0.0.1:6379 lrange mylist 0 -1 1) 6 2) 5 3) 4 4) 3 5) 2 6) 1 127.0.0.1:6379 linsert mylist after 4 100 (integer) 7 127.0.0.1:6379 lrange mylist 0 -1 1) 6 2) 5 3) 4 4) 100 5) 3 6) 2 7) 1 127.0.0.1:6379 linsert mylist before 4 200 (integer) 8 127.0.0.1:6379 lrange mylist 0 -1 1) 6 2) 5 3) 200 4) 4 5) 100 6) 3 7) 2 8) 1 127.0.0.1:6379 linsert mylit before 4 200 (integer) 0LLEN获取list长度。语法LLEN key命令有效版本1.0.0 之后时间复杂度O (1)返回值list的长度。示例127.0.0.1:6379 llen mylist (integer) 8 127.0.0.1:6379 llen mylis (integer) 0LREM移除列表中的元素。语法LREM key count elementcount —— 要删除的个数element —— 要删除的值命令有效版本1.0.0 之后时间复杂度O (n)其中 n 是列表的长度。返回值被移除元素的数量。在Redis的LREM命令中count参数指定了要移除元素的数量。这个参数可以是正数、负数或零其含义如下列表[A, B, A, C, A]LREM list 2 A→ 从左删 2 个 A → 剩[B, C, A]LREM list -2 A→ 从右删 2 个 A → 剩[A, B, C]LREM list 0 A→ 删掉所有 A → 剩[B, C]示例127.0.0.1:6379 lpush mylist 1 2 3 4 1 2 3 4 (integer) 8 127.0.0.1:6379 lrange mylist 0 -1 1) 4 2) 3 3) 2 4) 1 5) 4 6) 3 7) 2 8) 1 127.0.0.1:6379 lrem mylist -1 2 (integer) 1 127.0.0.1:6379 lrange mylist 0 -1 1) 4 2) 3 3) 2 4) 1 5) 4 6) 3 7) 1 127.0.0.1:6379 lrem mylist 1 2 (integer) 1 127.0.0.1:6379 lrange mylist 0 -1 1) 4 2) 3 3) 1 4) 4 5) 3 6) 1 127.0.0.1:6379 lrem mylist 0 3 (integer) 2 127.0.0.1:6379 lrange mylist 0 -1 1) 4 2) 1 3) 4 4) 1在这个示例中LREM命令从mylist列表中移除了最后一个匹配的value2元素。LTRIM修剪列表只保留指定区间的元素。语法LTRIM key start stop命令有效版本1.0.0 之后时间复杂度O (n)其中 n 是被移除元素的数量。返回值OK。示例127.0.0.1:6379 lpush mylist value1 value2 value3 value4 value5 (integer) 5 127.0.0.1:6379 ltrim mylist 1 3 OK 127.0.0.1:6379 lrange mylist 0 -1 1) value2 2) value3 3) value4在这个示例中LTRIM命令修剪了mylist列表只保留了索引 1 到 3包含的元素。LSET设置列表指定索引的值。语法LSET key index value命令有效版本1.0.0 之后时间复杂度O (n)其中 n 是索引位置之前的元素数量。返回值OK。示例127.0.0.1:6379 lpush mylist value1 value2 value3 (integer) 3 127.0.0.1:6379 lset mylist 1 newValue OK 127.0.0.1:6379 lrange mylist 0 -1 1) value1 2) newValue 3) value3在这个示例中LSET命令将mylist列表中索引 1 的元素设置为newValue。阻塞版本命令b---block阻塞blpop和brpop是lpop和rpop的阻塞版本和对应非阻塞版本的作用基本一致除了在列表中有元素的情况下阻塞和非阻塞表现是一致的。但如果列表中没有元素非阻塞版本会立即返回nil但阻塞版本会根据timeout阻塞一段时间期间Redis可以执行其他命令但要求执行该命令的客户端会表现为阻塞状态。命令中如果设置了多个键那么会从左向右进行遍历键一旦有一个键对应的列表中可以弹出元素命令立即返回。blpop和brpop都是可以同时去尝试获取多个key的列表的元素的多个key对应多个list这多个list哪个有元素了就会返回哪一个元素如果多个客户端同时对一个键执行pop则最先执行命令的客户端会得到弹出的元素。阻塞版本的blpop和非阻塞版本lpop的区别BLPOPLPOP的阻塞版本。语法BLPOP key [key ...] timeout可以指定一个或者多个key每一个key都对应一个list。如果这些list有任何一个为非空blpop都能够把里面的元素获取到立即返回。如果这些list都为空此时就需要阻塞等待等待其他客户端往这些list插入元素。此处还可以指定超时时间单位为秒Redis 6之后可以设置小数命令有效版本1.0.0 之后时间复杂度O (1)返回值取出的元素或者nil。示例redis EXISTS list1 list2 (integer) 0 redis RPUSH list1 a b c (integer) 3 redis BLPOP list1 list2 0 1) list1 2) aBRPOPRPOP的阻塞版本。语法BRPOP key [key ...] timeout命令有效版本1.0.0 之后时间复杂度O (1)返回值取出的元素或者nil。示例redis DEL list1 list2 (integer) 0 redis RPUSH list1 a b c (integer) 3 redis BRPOP list1 list2 0 1) list1 2) c命令小结有关列表的命令已经介绍完毕下表是这些命令的作用和时间复杂度开发人员可以参考。列表命令操作类型命令时间复杂度添加rpush key value [value ...]O(k)k 是元素个数添加lpush key value [value ...]O(k)k 是元素个数添加lpush key before | after pivot valueO(n)n 是 pivot 距离头尾的距离查找lrange key start en dO(sn)s 是 start 偏移量n 是 start 到 end 的范围查找lindex key indexO(n)n 是索引的偏移量查找llen keyO(1)删除lpop keyO(1)删除rpop keyO(1)删除lrem key count valueO(k)k 是元素个数删除ltrim key start endO(k)k 是元素个数修改lset key index valueO(n)n 是索引的偏移量阻塞操作blpop brpopO(1)内部编码列表类型的内部编码有两种ziplist压缩列表当列表的元素个数小于list-max-ziplist-entries配置默认 512 个同时列表中每个元素的长度都小于list-max-ziplist-value配置默认 64 字节时Redis 会选择用ziplist来作为列表的内部编码实现以此减少内存消耗。节省空间元素多了之后操作效率偏低linkedlist链表当列表类型无法满足ziplist的条件时Redis 会使用linkedlist作为列表的内部实现。上面这两种编码方式是 Redis 旧版本的编码方式现在新版本的编码方式是以quicklist数据结构来实现的它是ziplist和linkedlist的结合体整体还是一个双向链表结构但是链表的每一个节点都是一个压缩列表ziplist。旧版本设计规则当元素个数较少且没有超大元素时内部编码为ziplist127.0.0.1:6379 rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379 object encoding listkey ziplist当元素个数超过 512 个时内部编码转为linkedlist127.0.0.1:6379 rpush listkey e1 e2 e3 ... 省略 e512 e513 (integer) 513 127.0.0.1:6379 object encoding listkey linkedlist当某个元素的长度超过 64 字节时内部编码也会转为linkedlist127.0.0.1:6379 rpush listkey one string is bigger than 64 bytes ... 省略 ... (integer) 1 127.0.0.1:6379 object encoding listkey linkedlist新版本现在的设计127.0.0.1:6379 lpush mylist 1 2 3 4 5 6 (integer) 6 127.0.0.1:6379 type mylist list 127.0.0.1:6379 object encoding mylist quicklist使用场景消息队列如下图所示Redis 可以使用lpush brpop命令组合实现经典的阻塞式生产者-消费者模型队列生产者客户端使用lpush从列表左侧插入元素多个消费者客户端使用brpop命令阻塞式地从队列中“争抢”队首元素。通过多个客户端来保证消费的负载均衡和高可用性。Redis 阻塞消息队列模型分频道的消息队列如下图所示Redis 同样使用lpush brpop命令但通过不同的键模拟频道的概念不同的消费者可以通过brpop不同的键值实现订阅不同频道的理念。Redis 分频道阻塞消息队列模型多个列表/频道这种场景是非常常见的日常使用的一些程序比如说抖音有一些通道是用来传输端短视频数据的还有一个通道是用来传输弹幕的...搞成多个频道就可以在某种数据发生问题的时候不会对其他频道产生影响解耦合微博 Timeline每个用户都有属于自己的 Timeline微博列表现需要分页展示文章列表。此时可以考虑使用列表因为列表不但是有序的同时支持按照索引范围获取元素。每篇微博使用哈希结构存储例如微博中 3 个属性title、timestamp、content标题时间内容hmset mblog:1 title xx timestamp 1476536196 content xxxx ... hmset mblog:n title xx timestamp 1476536196 content xxxxx向用户 Timeline 添加微博user:uidlpush user:1:mblogs mblog:1 mblog:3 ... lpush user:k:mblogs mblog:9分页获取用户的 Timeline例如获取用户 1 的前 10 篇微博keylist lrange user:1:mblogs 0 9 for key in keylist { hgetall key }此方案在实际中可能存在两个问题1n 问题。即如果每次分页获取的微博个数较多需要执行多次hgetall操作此时可以考虑使用 pipeline流水线模式虽然咱们是多个 redis 命令但是把这些命令合并成一个网络请求进行通信批量提交命令或者微博不采用哈希类型而是使用序列化的字符串类型使用mget获取。分割获取文章时lrange在列表两端表现较好获取列表中间的元素表现较差此时可以考虑将列表做拆分。选择列表类型时请参考同侧存取lpushlpop或者rpushrpop为栈异侧存取lpushrpop或者rpushlpop为队列