redis-基础数据结构二

在前面的文章 redis-基础数据结构一 中介绍了 redis 基础数据结构中的两种,分别是字符串和哈希,今天我们来了解剩下的三种数据结构:列表,集合,有序集合。

列表

列表(list)类型是用来存储多个有序的字符串。在Redis中,可 以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等;另外列表中的元素可以是重复的。

总之它是一种比较灵活的数据结构,可以充当栈和队列的角色。

命令

添加操作

从右边插入元素

rpush key value [value ...]

从左边插入元素

lpush key value [value ...]

示例:

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"

向某个元素前或者后插入元素

linsert key before|after pivot value

注意:pivot 是元素的值,linsert 会从列表中找到第一个等于 pivot 的元素(从左往右),在其前(before)或者后(after)插入一个新的元素 value

示例:

127.0.0.1:6379> linsert listkey before b java 
(integer) 4
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "java"
3) "b"
4) "a"

返回结果为命令执行后,列表的长度。

查找

获取指定范围内的元素列表

lrange key start end

示例:

127.0.0.1:6379> lrange listkey 1 3
1) "java"
2) "b"
3) "a"

注意:第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。 第二,lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同


获取列表指定下标的元素

lindex key index

示例,获取当前列表最后一个元素

127.0.0.1:6379> lindex listkey -1
"a"
删除

从列表左侧弹出元素

lpop key

从列表右侧弹出元素

rpop key

示例:

127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "java"
3) "b"
4) "a"
127.0.0.1:6379> lpop listkey
"c"
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"

删除指定元素

lrem key count value

lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:

  • count>0,从左到右,删除最多count个元素
  • count<0,从右到左,删除最多count绝对值个元素
  • count=0,删除所有

示例,从左到右,删除 4 个 a

127.0.0.1:6379> lpush listkey a a a a a
(integer) 8
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "a"
3) "a"
4) "a"
5) "a"
6) "java"
7) "b"
8) "a"
127.0.0.1:6379> lrem listkey 4 a
(integer) 4
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
4) "a"

按照索引范围修剪列表

ltrim key start end

示例,下面操作是会保留第 1 个到 第 3 个元素

127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
4) "a"
127.0.0.1:6379> ltrim listkey 0 2
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
修改

修改指定索引下标的元素

lset key index newValue

示例

127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
127.0.0.1:6379> lset listkey 0 c
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "java"
3) "b"
阻塞操作

阻塞式弹出

blpop key [key ...] timeout 
brpop key [key ...] timeout

blpop和brpop是lpop和rpop的阻塞版本,它们除了弹出方向不同,使用方法基本相同,所以下面以brpop命令进行说明,brpop命令包含两个参数:

  • key[key…]:多个列表的键
  • ·timeout:阻塞时间(单位:秒)

如果列表为空,timeout=3,那么客户端要等到3秒后返回:

127.0.0.1:6379> blpop emptylist 3
(nil)
(3.04s)

如果timeout=0,那么客户端一直阻塞等下去(期间如果添加了数据ele,客户端立即返回):

127.0.0.1:6379> blpop emptylist 0
.....

列表不为空:客户端会立即返回:

127.0.0.1:6379> blpop listkey 0
1) "listkey"
2) "c"

注意:如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。如果多个客户端对同一个键执行brpop,那么最先执行brpop命 令的客户端可以获取到弹出的值,其它客户端继续阻塞。

内部编码

ziplist

当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使

用。

linkedlist

当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。

quicklist

Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现。

使用场景

消息队列

Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素。

文章列表

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

具体文章信息则是使用哈希存储,例如一个文章有 title,timestamp,content 三个属性:

hmset acticle:1 title xx timestamp 1476536196 content xxxx

列表中存储的则是对应哈希表中文章的键名

lpush user:1:acticles article:1

分页查询,并获取文章信息(伪代码)

articles = lrange user:1:articles 0 9
for article in {articles} 
    hgetall {article}

但需要注意,这种方式会存在两个问题。

1.如果每次分页获取的文章个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 Pipeline 批量获取,或者考虑将文章数据序列化为字符串类型,使用 mget 批量获取。

2.分页获取文章列表时,lrange命令在列表两 端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表做二级拆分。不过在 Redis3.2 以后的版本,加入了 quicklist 内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。

使用场景口诀
  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

集合

集合类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素

另外 Redis 除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。

命令(集合内)

添加元素
sadd key element [element ...]

返回结果为添加成功的元素个数,示例:

127.0.0.1:6379> exists set1
(integer) 0
127.0.0.1:6379> sadd set1 a b c
(integer) 3
127.0.0.1:6379> sadd set1 a b
(integer) 0
删除元素
srem key element [element ...]

返回结果为成功删除元素个数,示例:

127.0.0.1:6379> srem set1 a b
(integer) 2
127.0.0.1:6379> srem set1 d e
(integer) 0
计算元素个数
scard key

示例:

127.0.0.1:6379> scard set1
(integer) 3

scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量。

判断元素是否在集合中
sismember key element

如果给定元素element在集合内返回1,反之返回0,示例:

127.0.0.1:6379> scard set1
(integer) 3
127.0.0.1:6379> sismember set1 a
(integer) 1
127.0.0.1:6379> sismember set1 d
(integer) 0
随机从集合返回指定个数元素
srandmember key [count]

[count]是可选参数,如果不写默认为1,示例:

127.0.0.1:6379> srandmember set1
"a"
127.0.0.1:6379> srandmember set1 2
1) "c"
2) "b"
从集合随机弹出元素
spop key

示例:

127.0.0.1:6379> smembers set1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> spop set1
"b"
127.0.0.1:6379> smembers set1
1) "c"
2) "a"

Redis 从3.2版本开始,spop也支持[count]参数,示例:

127.0.0.1:6379> spop set1 2
1) "c"
2) "a"
127.0.0.1:6379> smembers set1
(empty list or set)

srandmember和spop都是随机从集合选出元素,两者不同的是spop命令执行后,元素会从集合中删除,而srandmember不会。

获取所有元素
smembers key

上面已经示范过了,需要注意smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻

塞Redis的可能性,这时候可以使用sscan来完成。

命令(集合间)

求多个集合交集
sinter key [key ...]

示例:

127.0.0.1:6379> sadd set1 a b c
(integer) 3
127.0.0.1:6379> sadd set2 b c d
(integer) 3
127.0.0.1:6379> sinter set1 set2
1) "c"
2) "b"
求多个集合的并集
suinon key [key ...]

示例:

127.0.0.1:6379> sunion set1 set2
1) "c"
2) "b"
3) "d"
4) "a"
求多个集合的差集
sdiff key [key ...]

示例:

127.0.0.1:6379> sdiff set1 set2
1) "a"

需要注意,结果返回的是第一个集合的差集元素(从左至右)。

将交集、并集、差集的结果保存
sinterstore destination key [key ...] 
suionstore destination key [key ...] 
sdiffstore destination key [key ...]

集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面 三个命令(原命令+store)将集合间交集、并集、差集的结果保存在 destination key。

示例:

127.0.0.1:6379> sinterstore set3 set1 set2
(integer) 2
127.0.0.1:6379> smembers set3
1) "c"
2) "b"

内部编码

intset

当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

示例:

127.0.0.1:6379> sadd intset1 1 2 3
(integer) 3
127.0.0.1:6379> object encoding intset1
"intset"
hashtable

当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

示例:

127.0.0.1:6379> sadd charset1 1 2 b
(integer) 3
127.0.0.1:6379> object encoding charset1
"hashtable"

使用场景

用户标签

例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签。现在各种应用的推荐功能,很大程度都是基于用户标签来实现的。

给用户添加标签:

sadd user:1:tags tag1 tag2 tag5 
sadd user:2:tags tag2 tag3 tag5

给标签添加用户

sadd tag1:users user:1 user:3 
sadd tag2:users user:1 user:2 user:3

有序集合

与集合一样,在有序集合中不允许元素重复,另外元素还可以排序。排序规则是依据每个元素的分数(score)属性实现的。

有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能。

注意:有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。

命令

添加成员
zadd key score member [score member ...]

示例:

127.0.0.1:6379> zadd zset1 30 a 40 b 50 c
(integer) 3

Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:

  • nx:member必须不存在,才可以设置成功,用于添加
  • xx:member必须存在,才可以设置成功,用于更新
  • ch:返回此次操作后,有序集合元素和分数发生变化的个数
  • incr:对score做增加,相当于后面介绍的zincrby

另外有序集合相比集合提供了排序字段,但是也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。

获取成员个数
zcard key
获取某个成员的分数
zscore key member

示例:

127.0.0.1:6379> zscore zset1 a
"30"
获取成员的排名
zrank key member 
zrevrank key member

zrank是从分数从低到高返回排名,zrevrank反之,示例(元素 a 正数第一,倒数第三):

127.0.0.1:6379> zrank zset1 a
(integer) 0
127.0.0.1:6379> zrevrank zset1 a
(integer) 2
删除成员
zrem key member [member ...]
增加成员分数
zincrby key increment member

示例:

127.0.0.1:6379> zincrby zset1 10 b
"50"
127.0.0.1:6379> zscore zset1 b
"50"
返回指定排名范围的成员
zrange key start end [withscores] 
zrevrange key start end [withscores]

有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。

示例(返回分数最低的前两个元素):

127.0.0.1:6379> zrange zset1 0 1
1) "b"
2) "c"

加上 withscores 还会返回成员的分数:

127.0.0.1:6379> zrange zset1 0 1 withscores
1) "b"
2) "50"
3) "c"
4) "50"
返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count] zrevrangebyscore key max min [withscores] [limit offset count]

其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。[limit offset count] 可以限制输出的起始位置和个数(分页)。

同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和 +inf分别代表无限小和无限大。

返回指定分数范围成员个数
zcount key min max
删除指定排名内的升序元素
zremrangebyrank key start end
删除指定分数范围的成员
zremrangebyscore key min max
集合间操作

zset 也同样支持集合间的运算操作:交集,并集,差集。 同时还支持聚合操作:max, min, sum 等。涉及的内容比较多,篇幅有限,把这个拆分到以后的系列文章中讲。

内部编码

ziplist

当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现。

skiplist

当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

使用场景

排行榜系统

有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。

总结

通过本篇文章,我们了解了:

  • 列表类型的常用命令,内部编码,使用场景
  • 集合类型的常用命令,内部编码,使用场景
  • 有序集合类型的常用命令,内部编码,使用场景

至此,关于 Redis 基础数据结构的总结暂时就结束了。如果觉得文章对你有帮助,欢迎留言点赞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值