内容简介:列表(列表是一种比较如图所示,
列表( list )类型是用来存储多个 有序 的 字符串 。在 Redis 中,可以对列表的 两端 进行 插入 ( push )和 弹出 ( pop )操作,还可以获取 指定范围 的 元素列表 、获取 指定索引下标 的 元素 等。
列表是一种比较 灵活 的 数据结构 ,它可以充当 栈 和 队列 的角色,在实际开发上有很多应用场景。
如图所示, a 、 b 、 c 、 d 、 e 五个元素 从左到右 组成了一个 有序的列表 ,列表中的每个字符串称为 元素 ( element ),一个列表最多可以存储 2 ^ 32 - 1 个元素。
- 列表的 插入 和 弹出 操作
- 列表的 获取 、 截取 和 删除 操作
正文
1. 相关命令
下面将按照对 列表 的 5 种 操作类型 对命令进行介绍:
1.1. 添加命令
1.1.1. 从右边插入元素
rpush key value [value ...]
下面代码 从右向左 插入元素 c 、 b 、 a :
127.0.0.1:6379> rpush listkey c b a (integer) 3 复制代码
lrange 0 -1 命令可以 从左到右 获取列表的 所有元素 :
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a" 复制代码
1.1.2. 从左边插入元素
lpush key value [value ...]
使用方法和 rpush 相同,只不过从 左侧插入 ,这里不再赘述。
1.1.3. 向某个元素前或者后插入元素
linsert key before|after pivot value
linsert 命令会从 列表 中找到 第一个 等于 pivot 的元素,在其 前 ( before )或者 后 ( after )插入一个新的元素 value ,例如下面操作会在列表的 元素 b 前插入 redis :
127.0.0.1:6379> linsert listkey before b redis (integer) 4 复制代码
返回结果为 4 ,代表当前 列表 的 长度 ,当前列表变为:
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b" 4) "a" 复制代码
1.2. 查询命令
1.2.1. 获取指定范围内的元素列表
lrange key start stop
lrange 操作会获取列表 指定索引 范围所有的元素。
索引下标有两个特点:
-
其一,索引下标 从左到右 分别是
0到N-1,但是 从右到左 分别是-1到-N。 -
其二,
lrange中的end选项包含了 自身 ,这个和很多编程语言不包含end不太相同。
从左到右获取列表的第 2 到第 4 个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey 1 3 1) "redis" 2) "b" 3) "a" 复制代码
从右到左获取列表的第 1 到第 3 个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey -3 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.2.2. 获取列表指定索引下标的元素
lindex key index
例如当前列表 最后一个 元素为 a :
127.0.0.1:6379> lindex listkey -1 "a" 复制代码
1.2.3. 获取列表长度
llen key
例如,下面示例 当前列表长度 为 4 :
127.0.0.1:6379> llen listkey (integer) 4 复制代码
1.3. 删除命令
1.3.1. 从列表左侧弹出元素
lpop key
如下操作将 列表 最左侧的元素 c 弹出,弹出后 列表 变为 redis 、 b 、 a 。
127.0.0.1:6379> lpop listkey "c" 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.3.2. 从列表右侧弹出元素
rpop key
它的使用方法和 lpop 是一样的,只不过从列表 右侧 弹出元元素。
127.0.0.1:6379> lpop listkey "a" 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "redis" 3) "b" 复制代码
1.3.3. 删除指定元素
lrem key count value
lrem 命令会从 列表 中找到 等于 value 的元素进行 删除 ,根据 count 的不同分为三种情况:
-
count > 0: 从左到右 ,删除最多
count个元素。 -
count < 0: 从右到左 ,删除最多
count绝对值 个元素。 -
count = 0, 删除所有 。
例如向列表 从左向右 插入 5 个 a ,那么当前 列表 变为 “a a a a a redis b a” ,下面操作将从列表 左边 开始删除 4 个为 a 的元素:
127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "redis" 3) "b" 4) "a" 复制代码
1.3.4. 按照索引范围修剪列表
127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "a" 复制代码
1.4. 修改命令
1.4.1. 修改指定索引下标的元素
修改 指定索引下标 的元素:
lset key index newValue
下面操作会将列表 listkey 中的第 3 个元素设置为 mysql :
127.0.0.1:6379> lset listkey 2 mysql OK 127.0.0.1:6379> lrange listkey 0 -1 1) "redis" 2) "b" 3) "mysql" 复制代码
1.5. 阻塞操作命令
阻塞式弹出操作的命令如下:
blpop key [key ...] timeout brpop key [key ...] timeout
blpop 和 brpop 是 lpop 和 rpop 的 阻塞版本 ,它们除了 弹出方向 不同, 使用方法 基本相同,所以下面以 brpop 命令进行说明, brpop 命令包含两个参数:
-
key[key...]:一个列表的 多个键 。
-
timeout: 阻塞 时间(单位: 秒 )。
对于 timeout 参数,要氛围 列表为空 和 不为空 两种情况:
- 列表为空
如果 timeout = 3 ,那么 客户端 要等到 3 秒后返回,如果 timeout = 0 ,那么 客户端 一直 阻塞 等下去:
127.0.0.1:6379> brpop list:test 3 (nil) (3.10s) 127.0.0.1:6379> brpop list:test 0 ...阻塞... 复制代码
如果此期间添加了数据 element1 ,客户端 立即返回 :
127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s) 复制代码
- 列表不为空 :客户端会 立即返回 。
127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1" 复制代码
在使用 brpop 时,有以下两点需要注意:
- 其一,如果是 多个键 ,那么
brpop会 从左至右 遍历键,一旦有 一个键 能 弹出元素 ,客户端 立即返回 :
127.0.0.1:6379> brpop list:1 list:2 list:3 0 ..阻塞.. 复制代码
此时另一个 客户端 分别向 list:2 和 list:3 插入元素:
client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1 复制代码
客户端会立即返回 list:2 中的 element2 ,因为 list:2 最先有 可以弹出 的元素。
127.0.0.1:6379> brpop list:1 list:2 list:3 0 1) "list:2" 2) "element2" 复制代码
- 其二,如果 多个客户端 对 同一个键 执行
brpop,那么 最先执行brpop命令的 客户端 可以 获取 到弹出的值。
按先后顺序在 3 个客户端执行 brpop 命令:
- 客户端1:
client-1> brpop list:test 0 ...阻塞... 复制代码
- 客户端2:
client-2> brpop list:test 0 ...阻塞... 复制代码
- 客户端3:
client-3> brpop list:test 0 ...阻塞... 复制代码
此时另一个 客户端 lpush 一个元素到 list:test 列表中:
client-lpush> lpush list:test element (integer) 1 复制代码
那么 客户端 1 会获取到元素,因为 客户端 1 最先执行 brpop 命令,而 客户端 2 和 客户端 3 会继续 阻塞 。
client> brpop list:test 0 1) "list:test" 2) "element" 复制代码
有关 列表 的 基础命令 已经介绍完了,下表是相关命令的 时间复杂度 :
2. 内部编码
列表类型的 内部编码 有两种:
2.1. ziplist(压缩列表)
当列表的元素个数 小于 list-max-ziplist-entries 配置(默认 512 个),同时列表中 每个元素 的值都 小于 list-max-ziplist-value 配置时(默认 64 字节), Redis 会选用 ziplist 来作为 列表 的 内部实现 来减少内存的使用。
2.2. linkedlist(链表)
当 列表类型 无法满足 ziplist 的条件时, Redis 会使用 linkedlist 作为 列表 的 内部实现 。
2.3. 编码转换
下面的示例演示了 列表类型 的 内部编码 ,以及相应的变化。
- 当元素 个数较少 且 没有大元素 时, 内部编码 为
ziplist:
127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist" 复制代码
- 当元素个数超过
512个, 内部编码 变为linkedlist:
127.0.0.1:6379> rpush listkey e4 e5 ... e512 e513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist" 复制代码
- 当某个元素超过
64字节 , 内部编码 也会变为linkedlist:
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte..." (integer) 4 127.0.0.1:6379> object encoding listkey "linkedlist" 复制代码
Redis3.2 版本提供了 quicklist 内部编码 ,简单地说它是以一个 ziplist 为 节点 的 linkedlist ,它结合了 ziplist 和 linkedlist 两者的优势,为 列表类型 提供了一种更为优秀的 内部编码 实现,它的设计原理可以参考 Redis 的另一个作者 Matt Stancliff 的博客redis-quicklist。
3. 应用场景
3.1. 消息队列
通过 Redis 的 lpush + brpop 命令组合 ,即可实现 阻塞队列 。如图所示:
生产者客户端使用 lrpush 从列表 左侧插入元素 , 多个消费者客户端 使用 brpop 命令 阻塞式 的 “抢” 列表 尾部 的元素, 多个客户端 保证了消费的 负载均衡 和 高可用性 。
3.2. 文章列表
每个 用户 有属于自己的 文章列表 ,现需要 分页 展示文章列表。此时可以考虑使用 列表 ,因为列表不但是 有序的 ,同时支持 按照索引范围 获取元素。
- 每篇文章使用 哈希结构 存储,例如每篇文章有
3个属性title、timestamp、content:
hmset acticle:1 title xx timestamp 1476536196 content xxxx hmset acticle:2 title yy timestamp 1476536196 content yyyy ... hmset acticle:k title kk timestamp 1476512536 content kkkk 复制代码
- 向用户文章列表 添加文章 ,
user:{id}:articles作为用户文章列表的 键 :
lpush user:1:acticles article:1 article:3 article:5 lpush user:2:acticles article:2 article:4 article:6 ... lpush user:k:acticles article:7 article:8 复制代码
- 分页 获取 用户文章列表 ,例如下面 伪代码 获取用户
id=1的前10篇文章:
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
复制代码
使用 列表 类型 保存 和 获取 文章列表会存在两个问题:
-
第一:如果每次 分页 获取的 文章个数较多 ,需要执行多次
hgetall操作,此时可以考虑使用Pipeline进行 批量获取 ,或者考虑将文章数据 序列化为字符串 类型,使用mget批量获取 。 -
第二: 分页 获取 文章列表 时,
lrange命令在列表 两端性能较好 ,但是如果 列表较大 ,获取列表 中间范围 的元素 性能会变差 。此时可以考虑将列表做 二级拆分 ,或者使用Redis 3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表 中间范围 的元素时也可以 高效完成 。
3.3. 其他场景
实际上列表的使用场景很多,具体可以参考如下:
| 命令组合 | 对应数据结构 |
|---|---|
| lpush + lpop | Stack(栈) |
| lpush + rpop | Queue(队列) |
| lpush + ltrim | Capped Collection(有限集合) |
| lpush + brpop | Message Queue(消息队列) |
小结
本文介绍了 Redis 中的 列表 的 一些 基本命令 、 内部编码 和 适用场景 。通过组合不同 命令 ,可以把 列表 转换为不同的 数据结构 使用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入剖析Redis系列(六) - Redis数据结构之哈希
- 深入剖析Redis系列(八) - Redis数据结构之集合
- 深入剖析Redis系列(四) - Redis数据结构与全局命令概述
- 深入剖析Redis系列(五) - Redis数据结构之字符串
- kafka集群Producer基本数据结构及工作流程深入剖析-kafka 商业环境实战
- 【Java集合源码剖析】ArrayList源码剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Information
James Gleick / Vintage / 2012-3-6 / USD 16.95
James Gleick, the author of the best sellers Chaos and Genius, now brings us a work just as astonishing and masterly: a revelatory chronicle and meditation that shows how information has become th......一起来看看 《The Information》 这本书的介绍吧!