redis 的有序集合(sorted set)是一个基于分数(score)排序的数据结构,它在 redis 中非常重要,常用于实现排行榜、近似计数器等功能。
redis 的有序集合(sorted set)是基于跳跃表(skip list)实现的。跳跃表是一种高效的数据结构,其插入、删除和查找操作的平均时间复杂度都是 o(log n),相对于平衡树(如红黑树)的实现要简单很多。跳跃表的结构类似于链表,每个节点除了保存元素值外,还包含一个指针数组,分别指向对应层次的下一个节点。这种多级指针的设计,使得跳表可以跨越多个节点进行快速搜索,同时保证跳表结构的高效性和简洁性。
有序集合的底层数据结构由哈希(hash)和跳跃表组成。在哈希中,存储了元素及其关联的评分(分数)。每个元素都有一个唯一的评分,用于确定其在跳跃表中的位置。当需要对有序集合进行操作时,redis 首先通过哈希表找到元素及其评分,然后通过跳跃表进行相应的操作。
以下是 redis 有序集合(sorted set)的一些核心操作及其对应的核心代码分析:
添加元素(zadd):
有序集合中的元素添加操作是通过哈希表和跳跃表协同完成的。首先,redis 将元素值和评分存储在哈希表中。然后,根据评分在跳跃表中找到对应的位置,并将新元素插入到该位置。
获取元素(zrange、zrevrange):
有序集合中的获取元素操作主要依赖于跳跃表。zrange 操作从跳跃表的头部开始,按照给定的评分范围返回符合条件的元素。zrevrange 操作则从跳跃表的尾部开始,按照给定的评分范围返回符合条件的元素。
删除元素(zrem):
删除元素操作首先通过哈希表找到对应元素,然后在跳跃表中删除该元素。redis 只需要删除哈希表中的指向该元素的指针,跳跃表中的元素会自动上移。
更新元素评分(zincrby):
更新元素评分操作仅需修改哈希表中对应元素的评分,然后重新计算跳跃表中元素的位置。
获取有序集合长度(zcard):
有序集合长度的操作直接查询哈希表中的键值对数量。
随机获取元素(zrandmember):
随机获取元素操作首先从哈希表中随机选择一个元素,然后在该元素所在的跳跃表区间内随机选择一个元素。
通过以上操作,redis 实现了高效有序集合(sorted set)的数据结构,提供了高性能的排序和范围查找功能。
2、实战
要使用 spring boot 和 redis 实现排行榜功能,你可以遵循以下步骤:
引入依赖
在你的 spring boot 项目的 pom.xml 文件中,添加以下依赖:
org.springframework.boot spring-boot-starter-data-redis
配置 redis
在 application.properties 或 application.yml 文件中配置 redis 连接信息:
# application.properties spring.redis.host=localhost spring.redis.port=6379 # application.yml spring: redis: host: localhost port: 6379
创建 redis 模板
创建一个 redistemplate bean:
import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.data.redis.connection.redisconnectionfactory; import org.springframework.data.redis.core.redistemplate; import org.springframework.data.redis.serializer.genericjackson2jsonredisserializer; import org.springframework.data.redis.serializer.stringredisserializer;@configuration public class redisconfig { @bean public redistemplate redistemplate(redisconnectionfactory redisconnectionfactory) { redistemplate redistemplate = new redistemplate(); redistemplate.setconnectionfactory(redisconnectionfactory); redistemplate.setkeyserializer(new stringredisserializer()); redistemplate.setvalueserializer(new genericjackson2jsonredisserializer()); redistemplate.sethashkeyserializer(new stringredisserializer()); redistemplate.sethashvalueserializer(new genericjackson2jsonredisserializer()); return redistemplate; } }
创建排行榜实体类
创建一个排行榜实体类,包含用户 id、分数等信息:
import java.io.serializable;public class rankingentity implements serializable { private string userid; private double score; // 构造方法、getter 和 setter
实现 redis 排行榜操作
创建一个服务类,实现排行榜的相关操作,如添加分数、查询排名等:
import org.springframework.beans.factory.annotation.autowired; import org.springframework.data.redis.core.redistemplate; import org.springframework.data.redis.core.valueoperations; import org.springframework.stereotype.service;import java.util.concurrent.timeunit;@service public class rankingservice { @autowired private redistemplate redistemplate; private static final string ranking_key = ranking_list; /** * 添加分数 * @param userid 用户 id * @param score 分数 */ public void addscore(string userid, double score) { valueoperations valueoperations = redistemplate.opsforvalue(); valueoperations.set(ranking_key + : + userid, score, 60, timeunit.seconds); } /** * 查询排名 * @return 排名列表 */ public list getrankinglist() { list rankinglist = redistemplate.opsforlist().range(ranking_key, 0, -1); return rankinglist; } /** * 查询用户排名 * @param userid 用户 id * @return 用户排名 */ public object getuserranking(string userid) { return redistemplate.opsforvalue().get(ranking_key + : + userid); } * @param userid 用户 id */ public void deleteuserscore(string userid) { valueoperations valueoperations = redistemplate.opsforvalue(); valueoperations.delete(ranking_key + : + userid); } /** * 更新用户分数 * @param userid 用户 id * @param score 新的分数 */ public void updateuserscore(string userid, double score) { valueoperations valueoperations = redistemplate.opsforvalue(); valueoperations.set(ranking_key + : + userid, score, 60, timeunit.seconds); } /** * 获取用户排名列表的长度 * @return 用户排名列表的长度 */ public long getuserrankinglistsize() { return redistemplate.opsforlist().size(ranking_key); } /** * 在用户排名列表中插入用户分数 * @param userid 用户 id * @param score 分数 * @param index 插入位置,0 表示插入到列表头部,负数表示插入到列表尾部 */ public void insertuserscore(string userid, double score, long index) { list rankinglist = redistemplate.opsforlist().range(ranking_key, 0, -1); redistemplate.opsforlist().leftpush(ranking_key, score, index); } /** * 在用户排名列表中删除用户分数 * @param userid 用户 id * @param index 删除位置,0 表示删除第一个元素,1 表示删除第二个元素,依此类推 */ public void deleteuserscore(string userid, long index) { list rankinglist = redistemplate.opsforlist().range(ranking_key, 0, -1); redistemplate.opsforlist().rightpop(ranking_key, index); } /** * 获取用户排名列表中的最后一个元素 * @return 用户排名列表中的最后一个元素 */ public object getlastuserscore() { return redistemplate.opsforlist().rightpop(ranking_key); } /** * 获取用户排名列表中的第一个元素 * @return 用户排名列表中的第一个元素 */ public object getfirstuserscore() { return redistemplate.opsforlist().leftpop(ranking_key); } /** * 在用户排名列表中添加元素 * @param score 添加的分数 */ public void adduserscore(double score) { redistemplate.opsforlist().rightpush(ranking_key, score); } /** * 在用户排名列表中删除元素 * @param index 删除位置,0 表示删除第一个元素,1 表示删除第二个元素,依此类推 */ public void deleteuserscore(long index) { redistemplate.opsforlist().rightpop(ranking_key, index); } /** * 获取用户排名列表 * @return 用户排名列表 */ public list getuserrankinglist() { return redistemplate.opsforlist().range(ranking_key, 0, -1); } /** * 设置用户排名列表的长度 * @param length 用户排名列表的新长度 */ public void setuserrankinglistlength(long length) { redistemplate.opsforlist().setsize(ranking_key, length); } /** * 获取用户排名 * * @param userid 用户 id * @return 用户排名,如果用户不存在,返回 -1 */ public int getuserranking(string userid) { list rankinglist = getrankinglist(); object userscore = getuserranking(userid); if (userscore == null) { return -1; } int rank = 0; for (object score : rankinglist) { if (score.equals(userscore)) { break; } rank++; } return rank; } /** * 获取用户排名列表中的指定位置的元素 * * @param index 指定位置,从 0 开始 * @return 用户排名列表中的指定位置的元素 */ public object getuserrankinglistelement(long index) { return redistemplate.opsforlist().range(ranking_key, 0, -1).get(index); } /** * 获取用户排名列表中的用户分数 * * @param userid 用户 id * @return 用户排名列表中的用户分数,如果用户不存在,返回 null */ public object getuserranking(string userid) { valueoperations valueoperations = redistemplate.opsforvalue(); return valueoperations.get(ranking_key + : + userid); } /** * 是否存在用户 * * @param userid 用户 id * @return 是否存在用户 */ public boolean existsuser(string userid) { valueoperations valueoperations = redistemplate.opsforvalue(); return valueoperations.haskey(ranking_key + : + userid); } /** * 清除所有用户排名数据 */ public void clearalluserrankingdata() { redistemplate.delete(ranking_key); } }
AMD EPYC处理器随斩获新的超级计算及高性能云计算系统而扩张部署
无线电源传输的IIoT传感器网络电缆解决方案
柔性电路板制造–如何选择合适的PCB板材料?
一加7T系列大受欢迎,刘作虎称已经加了三次单
喜报 | 广凌科技 广东交通职院清远校区智慧教室顺利通过验收!
使用Redis的有序集合实现排行榜功能和Spring Boot集成
数字资产量化交易软件开发自动化交易系统开发
AI换脸不断引来争议,需要不断完善
乐视新旗舰X850曝光,将于4月11日发布:骁龙821+双摄
LED日光灯用光源发展趋势
汽车dsp接线需要注意些什么
如何理解实时仿真
听说过苹果iMessage转账功能吗?
定频风管机报E3保护到底怎么回事?
采用AI智能分析的智慧采购系统,你见过吗?
橡胶输送带开裂原因及解决方法
瑞萨电子推出超低功耗ZMOD4510户外空气质量传感器平台,解锁个性化空气质量监测体验
盘点华灿光电LED显示芯片的战略布局
5G手机年底有望迎来爆发期,2B业务将成新蓝海
谷歌翻译对比有道翻译东北话,高下立见!