redis有序集合键(数据结构篇)

有序集合(soted set / zset)

按照元素的分值来有序地储存各不相同的元素。
有序集合和集合一样,都可以包含任意数量的、各不相同的元素( element),不同于集合的是,有序集合的每个元素都关联着一个浮点数格式的分 值(score),并且有序集合会按照分 值,以从小到大的顺序来排列有序集合中的各个元素。虽然有序集合中的每个元素都必 须是各不相同的,但元素的分 值并没有这一限制,换句话来说,两个不同元素的分值可以是相同的。

有序集合示例

在这里插入图片描述

基本操作

添加元素、删除元素、返回指定元素的分 值、返回集合包含的元素数量,等等。

添加元素

ZADD key score element [[score element] [score element] …]
按照给定的分值和元素,将任意数量的元素添加到有序集合里面,命令的返回 值为成功添加的元素数
量。

redis> ZADD fruits-price 3.2 香蕉
1
redis> ZADD fruits-price 2.0 西瓜
1
redis> ZADD fruits-price 4.0 番石榴 7.0 梨 6.8 芒果 
3

复杂度 O(M*log(N)),其中 N 为有序集合已有的元素数量, M 为成功添加的新元素数量。
在这里插入图片描述

删除元素

ZREM key element [element …]
从有序集合中删除指定的元素,以及这些元素关联的分值,命令返回被成功删除的元素数量。

redis> ZADD fruits-price …
5
redis> ZREM fruits-price 番石榴 梨 芒果
3
redis> ZREM fruits-price 西瓜
1

复杂度 O(M*log(N)),其中 N 为有序集合已有的元素数量, M 为成功删除的元素数量。
在这里插入图片描述

返回元素的分值

ZSCORE key element
返回有序集合中,指定元素的分值。

redis> ZSCORE fruits-price 西瓜
2
redis> ZSCORE fruits-price 香蕉
3.2000000000000002
redis> ZSCORE fruits-price 芒果
6.7999999999999998

复杂度为 O(1) 。
在这里插入图片描述

增加或减少元素的分值

ZINCRBY key increment element
为有序集合指定元素的分 值加上增量 increment ,命令返回执行操作之后,元素的分值。没有相应的 ZDECRBY 命令,但可以通过将 increment 设置为负数来减少分值。

redis> ZINCRBY fruits-price 1.5 西瓜
3.5
redis> ZINCRBY fruits-price -0.8 香蕉
2.4000000000000004

复杂度为 O(log(N)) 。
在这里插入图片描述

返回有序集合的基数

ZCARD key
返回有序集合包含的元素数量(基数)。复 杂度为 O(1) 。

redis> ZADD fruits-price 3.2 香蕉
1
redis> ZCARD fruits-price
1
redis> ZADD fruits-price 2.0 西瓜
1
redis> ZCARD fruits-price
2
redis> ZADD fruits-price 4.0 番石榴 7.0 梨 6.8 芒果 
3
redis> ZCARD fruits-price
5

在这里插入图片描述

返回元素的排名(rank)

ZRANK key element
返回指定元素在有序集合中的排名,其中 排名按照元素的分值从小到大计算。排名以 0 开始。

redis> ZRANK fruits-price 西瓜
0
redis> ZRANK fruits-price 番石榴
2
redis> ZRANK fruits-price 芒果
3 

复杂度为 O(log(N)) 。
在这里插入图片描述

返回元素的逆序排名(reverse rank)

ZREVRANK key member
返回成员在有序集合中的逆序排名,其中 排名按照元素的分值从大到小计算。排名以 0 开始。

redis> ZREVRANK fruits-price 西瓜
4
redis> ZREVRANK fruits-price 番石榴
2
redis> ZREVRANK fruits-price 芒果

1复杂度为 O(log(N)) 。
在这里插入图片描述

分值范围操作

基于有序集合的排序性质,对处于某种分值范围之内的多个元素进行操作

获取指定索引范围内的升序元素

ZRANGE key start stop [WITHSCORES]
返回有序集合在按照分值从小到大排列元素(升序排列) 的情况下,索引 start 至索引 stop 范围之内的所有元素。
两个索引都可以是正数或者 负数。当给定 WITHSCORES 选项时,命令会将元素和分值一并返回。

redis> ZRANGE fruits 0 2
西瓜
香蕉
番石榴
redis> ZRANGE fruits -5 -4
西瓜
香蕉

命令的复杂度为 O(log(N)+M) ,N 为有序集合的基数,而 M 则为被返回元素的数量。
在这里插入图片描述

获取指定索引范围内的降序元素

ZREVRANGE key start stop [WITHSCORES]
返回有序集合在按照分值从大到小排列元素(降序排列) 的情况下,索引 start 至索引 stop 范围之内的所
有元素。
两个索引都可以是正数或者 负数。当给定 WITHSCORES 选项时,命令会把元素和分值一并返回。

redis> ZREVRANGE fruits 0 2
梨
芒果
番石榴
redis> ZREVRANGE fruits -5 -4
梨
芒果

命令的复杂度为 O(log(N)+M) ,N 为有序集合的基数,而 M 则为被返回元素的数量。
在这里插入图片描述

获取指定分值范围内的升序元素

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
返回有序集合在按照分值升序排列元素的情况下,分值在 min 和 max 范围之内的所有元素。
给定 WITHSCORES 选项时,元素和分值会一并返回。给定 LIMIT 选项时,可以通过 offset 参数指定返回的结果集要跳过多少个元素,而 count 参数则用于指定返回的元素数量。

redis> ZRANGEBYSCORE fruits 3.0 7.0 
香蕉
番石榴
芒果
梨

命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被返回元素的数量。
在这里插入图片描述

获取指定分值范围内的降序元素

ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
返回有序集合在按照分值降序排列元素的情况下,分值在 min 和 max 范围之内的所有元素。
给定 WITHSCORES 选项时,元素和分值会一并返回。给定 LIMIT 选项,可以通过 offset 参数指定返回的结果集要跳过多少个元素,而 count 参数则用于指定返回的元素数量。

redis> ZREVRANGEBYSCORE fruits 7.0 3.0
梨
芒果
番石榴
香蕉

命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被返回元素的数量。
在这里插入图片描述

计算给定分值范围内的元素数量

ZCOUNT key min max
返回有序集合在升序排列元素的情况下,分 值在 min 和 max 范围内的元素数量。

redis> ZCOUNT fruits 3.0 7.0
4
redis> ZCOUNT fruits 5.0 10.0
2

命令的复杂度为 O(log(N)), N 为有序集合的基数。
在这里插入图片描述

移除指定排名范围内的升序排列元素

ZREMRANGEBYRANK key start stop
移除有序集合中,元素按升序 进行排列的情况下,指定排名范 围内的所有元素。
排名范围可以使用正数和负数。

redis> ZREMRANGEBYRANK fruits 0 2
(integer) 3
redis> ZRANGE fruits 0 -1
芒果
梨

命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被移除元素的数量。
在这里插入图片描述

移除指定分值范围内的升序排列元素

ZREMRANGEBYSCORE key min max
移除有序集合中,分值范围介于 min 和 max 之内的所有元素。

redis> ZREMRANGEBYSCORE fruits 3.0 5.0
2
redis> ZRANGE fruits 0 -1
西瓜
芒果
梨

命令的复杂度为 O(log(N)+M),其中 N 为有序集合的基数,而 M 为被移除元素的数量。
在这里插入图片描述

示例:排行榜

在这里插入图片描述
上图是网易云音乐的几个排行榜,通过这些排行榜,用户可以快速地了解目前最受欢迎的是哪些歌曲。通过使用 Redis 的有序集合,我们也可以实现这样的排行榜。举个例子,我们可以将每首歌的名字储存为元素,每首歌的播放次数 储存 为分值,并调用 ZREVRANGE 来获取播放次数最多的那些歌曲,以此来实现一个“最多播放排行榜”。

排行榜的 API 及其实现

在这里插入图片描述
这个排行榜程序的实现源码可以在 rank_list.py 看到。

rank_list.py
# encoding: utf-8

class RankList:

    def __init__(self, key, client):
        self.key = key
        self.client = client

    def incr(self, item, increment=1):
        # 注意在 redis-py 客户端中
        # zincrby() 方法是先给定元素后给定分值
        # 这和 ZINCRBY 命令规定的顺序正好相反
        self.client.zincrby(self.key, item, increment)

    def get_top(self, n, show_score=False):
        return self.client.zrevrange(self.key, 0, n-1, withscores=show_score)
排行榜程序的使用示例
# 创建一个歌曲播放次数排行榜
list = RankList('most played songs', client)
# 每当某首歌曲被播放时,我们就将它的播放次数(分 值)增一
list.incr('平凡之路') # 播放次数 = 1
list.incr('后会无期') # 播放次数 = 1
list.incr('小苹果') # 播放次数 = 1
list.incr('小苹果') # 播放次数 = 2
list.incr('女儿情') # 播放次数 = 1
# …
# 获取播放次数前 10 位的歌曲
list.get_top(10)
示例:网站翻页

在这里插入图片描述
新浪博客的翻页程序,这样的翻页程序在新闻网站、博客、论坛、搜索引擎这些需要阅览大量条目的地方非常常 见。 对于很多数据库来说,实现分页功能通常是一件麻烦并且低效的事情,而使用 Redis 实 现翻页却非常简单,并且速度也很快(对数复杂度)。

翻页程序的 API 及其实现

在这里插入图片描述
这个翻页程序的实现源码可以在 paging.py 看到。

paging.py
# encoding: utf-8

class Paging:

    def __init__(self, key, client):
        """
        设置储存分页数据的键。
        """
        self.key = key
        self.client = client

    def add_item(self, item, post_time):
        """
        将条目 item 添加到分页数据里面,
        post_time 指定了该条目的发表时间。
        """
        self.client.zadd(self.key, item, post_time)

    def get_page(self, n, count, showtime=False):
        """
        以 count 个条目为一页,返回位于第 n 页的条目。
        """
        start_index = (n-1)*count
        end_index = n*count-1
        return self.client.zrevrange(self.key, start_index, end_index, withscores=showtime)
翻页程序的使用示例
# 对博客文章进行分页
paging = Paging('blog::paging', client)
# 添加博客文章的名字,以及 这些文章的发布时间
paging.add_item('今天天气不错', 1407000000)
paging.add_item('几个 Redis 使用示例', 1450000000)
paging.add_item('Redis in Action 读书笔记', 1560000000)
# …
# 以 5 个条目为一页,返回位于第 1 页的条目
paging.get_page(1, 5)
# 以 10 个条目为一页,返回位于第 3 页的条目
paging.get_page(3, 10)
示例:自动补完(auto complete)

在这里插入图片描述
上图展示了 Google 的自动补完功能,这个功能会列举出最经常被使用的一些关键字,帮助用户尽快地找到自己想要搜索的内容。通过使用 Redis 的有序集合,我们也可以实现一个这样的自动补完功能。

使用有序集合来储存提示列表

自动补完功能所展示的提示列表, 实际上就是一个有序集合,集合中 储存了按照使用次数从多到少进行排列的多个选项。 举个例子,键入‘周’这个字,自动补完程序返回的可能是这样一个有序集合:
在这里插入图片描述

构建提示列表

为了构建按使用次数进行排序的提示列表,程序需要分析用 户的输入,并记录每个字、词、句的使用频率。
举个例子,每当用户输入‘周杰伦’的时候,程序会执行以下命令:

ZINCRBY 'auto-complete::周' '周杰伦' 1
ZINCRBY 'auto-complete::周杰' '周杰伦' 1
ZINCRBY 'auto-complete::周杰伦' '周杰伦' 1
又比如说,每当用户输入‘周公解梦’的时候,程序会执行以下命令:
ZINCRBY 'auto-complete::周' '周公解梦' 1
ZINCRBY 'auto-complete::周公' '周公解梦' 1
ZINCRBY 'auto-complete::周公解' '周公解梦' 1
ZINCRBY 'auto-complete::周公解梦' '周公解梦' 1
获取提示列表

在刚开始的时候,因为程序没有创建任何提示列表,所以最先使用系 统的用户不会得到任何提示,但是随着用户不断地键入各式各样的输入,一个又一个提示列表就会被 创建出来,自动补完列出的提示信息就会变得越来越完整、越来越正确。
当用户键入相应的字、词、句时,程序可以通过访问以 ‘auto-complete::’ 开头的键来得到相应的提示信息。
举个例子,当用户键入 ‘周’ 字的时候,程序就会对 ‘auto-complete::周’ 这个有序集合调用ZREVRANGE 命令,返回以 ‘周’ 字开头,最常被输入的字、词、句。

自动补完程序的 API 及其实现

在这里插入图片描述
这个补完程序的实现代码可以在 auto_complete.py 看到。

auto_complete.py
# encoding: utf-8

class AutoComplete:

    def __init__(self, client):
        self.client = client

    def feed(self, phrase):
        # 对于输入 u'周杰伦' 来说
        # 这个 for 循环会执行以下命令:
        # ZINCRBY u'auto-complete::周'     u'周杰伦' 1
        # ZINCRBY u'auto-complete::周杰'   u'周杰伦' 1
        # ZINCRBY u'auto-complete::周杰伦' u'周杰伦' 1
        for i in range(len(phrase)+1):
            key = 'auto-complete::' + phrase[:i]
            self.client.zincrby(key, phrase, 1)

    def get_hint(self, phrase, n):
        key = 'auto-complete::' + phrase
        result = []
        for hint in self.client.zrevrange(key, 0, n-1):
            result.append(unicode(hint, 'utf-8'))
        return result
自动补完程序的使用示例
# 为自动补完程序设置客户端
ac = AutoComplete(client)
# 系统刚开始运作,没有任何提示信息
ac.get_hint(u'周', 5) # => []
# 记录用户键入的字、词、句,构建起提示列表
ac.feed(u'周杰伦')
ac.feed(u'周公解梦')
ac.feed(u'周迅')
# ...
# 获取提示信息
ac.get_hint(u'周', 5) # => [u'周永康', u'周公解梦', u'周杰伦', u'周迅', u'周星驰']

集合运算操作

计算并储存多个有序集合的交集、并集运算 结果

计算并集和交集

在这里插入图片描述
计算结果会被储存到 destkey 里面,命令返回结果集的基数。
numkeys 参数指定要进行计算的有序集合个数,key [key …] 指定进行计算的各个有序集合。

ZUNIONSTORE 命令示例
redis> ZADD fruits-8-13 300 "apple" 200 "banana" 150 "cherry" # 8 月 13 日水果销量
(integer) 3
redis> ZADD fruits-8-14 250 "apple" 300 "banana" 100 "cherry" # 8 月 14 日水果销量
(integer) 3
redis> ZUNIONSTORE fruits-8-13&14 2 fruits-8-13 fruits-8-14 # 计算 8 月 13 日和 14 日的总水果销量
(integer) 3
redis> ZRANGE fruits-8-13&14 0 -1 WITHSCORES
1) "cherry"
2) "250"
3) "banana"
4) "500"
5) "apple"
6) "550"
示例:使用并集计算实现周榜、月榜、年榜

在这里插入图片描述

示例:使用并集计算实现周榜、月榜、年榜

在前面的内容中,我们已经知道了如何使用 Redis 来构建单个排行榜,要创建周榜、月榜或者年榜,我们可以为每天都创建一个日排行榜,然后通 过对多个日排行榜进行并集计算,以此来得出周榜、月榜和年榜。
举个例子,为了计算 8 月 11 日至 8 月 17 日这一周的图书销量周榜,我们可以为这一周的每天都创建一个记录当日图书销量的排行榜,然后只要 对这七天的排行榜进行并集计算,就可以得出一周的 图书销量排行榜:

ZUNIONSTORE 'week_rank::8.11-8.17' 7 'day_rank::8.11' 'day_rank::8.12' ... 'day_rank::8.17'

与此类似,要计算月排行榜,我们需要对一个月内,每天的排行榜 进行并集计算(或者用四周的周榜加上几天的日榜);而要计算年排行榜,我们则需要对一年内十二个月的月排行榜 进行并集计算。
注意,如果参与并集计算的集合比较多,那么 Redis 服务器可能会被阻塞,因此最好在空余 时间或者备用服务器上进行计算。

复习

一个有序集合可以包含任意多个各不相同的元素,并且每个元素都关 联着一个浮点数格式的分值,集合内的各个元素会按照分值的大小,以从小到大的 顺序来进行排列。
在这里插入图片描述

发布了252 篇原创文章 · 获赞 151 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104243620