概述
有序集合和集合类似,只是说它是有序的,和无序集合的主要区别在于每一个元素除了值之外,它还会多一个分数。
- 分数是一个浮点数,在 Java 中是使用双精度表示的,根据分数, Redis 就可以支持对分数从小到大或者从大到小的排序
- 和无序集合一样,对于每一个元素都是唯一的 ,但是对于不同元素而言,它的分数可以一样
- 元素也是 String 数据类型,也是一种基于 hash 的存储结构。
- 集合是通过哈希表实现的,所以添加、删除、 查找的复杂度都是 0(1)
- 集合中最大的成员数为 2的32次方减 1 ( 40 多亿个成员)
有序集合的数据结构
有序集合是依赖 key 标示它是属于哪个集合,依赖分数进行排序,所以值和分数是必须的,而实际上不仅可以对分数进行排序,在满足一定的条件下,也可以对值进行排序 。
Redis 有序集合的部分命令
官网: https://redis.io/commands#sorted_set
有序集合和无序集合的命令是接近的,只是在这些命令的基础上,会增加对于排序的操作,这些是我们在使用的时候需要注意的细节.
有些时候 Redis 借助数据区间的表示方法来表示包含或者不包含,比如在数学的区间表示中[2,5 ]表示包含 2,但是不包含 5 的 区间。
命令 | 说明 | 备注 |
---|---|---|
zadd key score1 value1 [score2 value2 …] | 向有序集合的 key,增加一个或者多个成员 | 如果不存在对应的 key,则创建键为 key 的有序集合 |
zcard key | 获取有序集合的成员数 | ----- |
zcount key min max | 根据分数返回对应的成员列表 | min 为最小值, max为最大值,默认为包含min 和 max 值,采用数学区间表示的方法,如果需要不包含,则在分数前面加入“(”,注意不支持“[”表示 |
zincrby key increment member | 给有序集合成员值为 member 的分数增加 increment | ----- |
zinterstore desKey nurnkeys key1 [key2 key3 …] | 求多个有序集合的交集,并且将结果保存到 des Key 中 | numkeys 是一个整数,表示多少个有序集合 |
zlexcount key min max | 求有序集合 key 成员值在 min 和 max 的范围 | 这里范围为 key 的成员值, Redis 借助数据区间的表示方法,“[”表示包含该值,“(”表示不包含该值 |
zrange key start stop [withscores] | 按照分值的大小〈从小到大)返回成员,加入 start 和 stop 参数可以截取某一段返回.如果输入可选项 withscores,则连同分数一起返回 | 这里记集合最大长度为len,Redis 会将集合排序后,形成一个从 0 到len-1的下标,然后根据 start 和 stop 控制的下标(包含 start 和 stop)返回 |
zrank key member | 按从小到大求有序集合的排行 | 排名第一的为 0,第二的为 1 … |
zrangebylex key min max [limit offset count] | 根据值的大小,从小到大排序, min 为最小值, max 为最大值;limit 选项可选,当 Red is 求出范围集合后,会生产下标0到n,然后根据偏移量offset 和限定返回 数 count,返回对应的成员 | 这里范围为 key 的成员值, Red i s 借助数学区间的表示方法,“[”表示包含该值,“(”表示不包含该值 |
zrangebyscore key min max [withscores] [limit offset count] | 根据分数大小,从小到大求取范围,选项 withscores 和 limit 请参考 zrange 命令和zrangebylex 说明 | 根据分析求取集合的范围。这里默认包含 min和 max,如果不想包含,则在参数前加入“(”,注意不支持“ [”表示 |
zremrangebyscore key start stop | 根据分数区间进行删除 | 按照 socre 进行排序,然后排除 0 到len-1的下标,然后根据 start 和 stop 进行删除, Redis 借助数学区间的表示方法,“[”表示包含该值,“(”表示不包含该值 |
zremrangebyrank key start stop | 按照分数排行从小到大的排序删除,从0开始计算 | ----- |
zremrangebylex key min max | 按照值的分布进行删除 | ----- |
zrevrange key start stop [withscores] | 从大到小的按分数排序,参数请参见zrange | 与 zrange 相同,只是排序是从大到小 |
zrevrangebyscore key max min [withscores] | 从大到小的按分数排序,参数请参见zrangebyscore | 与 zrangebyscore 相同 ,只是排序是从大到小 |
zrevrank key member | 按从大到小的顺序,求元素的排行 | 排名第一位 0,第二位1 … |
zscore key member | 返回成员的分数值 | 返回成员的分数 |
zunionstore desKey numKeys key1 [key2 key3 key4 …] | 求多个有序集合的并集,其中 numKeys是有序,集合的个数 | ----- |
在对有序集合、下标、区间的表示方法进行操作的时候,需要十分小心命令,注意它是操作分数还是值,稍有不慎就会出现问题。
# 为了测试的数据干净,删除当前db的数据
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379>
127.0.0.1:6379>
# zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9个成员
127.0.0.1:6379> ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9
(integer) 9
# zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9个成员
127.0.0.1:6379> ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9
(integer) 9
# zcard key 获取有序集合zset1的成员数
127.0.0.1:6379> ZCARD zset1
(integer) 9
# zcount key min max 根据分数返回对应的成员列表
127.0.0.1:6379> ZCOUNT zset1 1 4
(integer) 4
# zinterstore desKey nurnkeys key1 [key2 key3 …] 求多个有序集合的交集,并且将结果保存到 des Key 中
127.0.0.1:6379> ZINTERSTORE des_key 2 zset1 zset2
(integer) 4
# zlexcount key min max 求有序集合 zset1 成员值在 min 和 max 的范围 [ 表示包含该值,( 表示不包含该值
127.0.0.1:6379> ZLEXCOUNT zset1 (x1 [x5
(integer) 4
# zrange key start stop [withscores] 按照分值的大小(从小到大)返回成员,加入 start 和 stop 参数可以截取某一段返回.如果输入可选项 withscores,则连同分数一起返回
127.0.0.1:6379> ZRANGE zset1 1 5 withscores
1) "x2"
2) "2"
3) "x3"
4) "3"
5) "x4"
6) "4"
7) "x5"
8) "5"
9) "x6"
10) "6"
# zrank key member 按从小到大求有序集合的排行
127.0.0.1:6379> ZRANK zset1 x5
(integer) 4
# zrangebylex key min max [limit offset count]根据值的大小,从小到大排序 [表示包含该值 (表示不包含该值
127.0.0.1:6379> ZRANGEBYLEX zset1 (x1 [x6
1) "x2"
2) "x3"
3) "x4"
4) "x5"
5) "x6"
127.0.0.1:6379>
# zrangebyscore key min max [withscores] [limit offset count] 根据分数大小,从小到大求取范围
127.0.0.1:6379> ZRANGEBYSCORE zset1 5 7
1) "x5"
2) "x6"
3) "x7"
# zrangebyscore key min max [withscores] [limit offset count] 根据分数大小,从小到大求取范围
127.0.0.1:6379> ZRANGEBYSCORE zset1 2 7 withscores limit 1 5
1) "x3"
2) "3"
3) "x4"
4) "4"
5) "x5"
6) "5"
7) "x6"
8) "6"
9) "x7"
10) "7"
# zrevrange key start stop [withscores] 从大到小的按分数排序
127.0.0.1:6379> ZREVRANGE zset1 1 5
1) "x8"
2) "x7"
3) "x6"
4) "x5"
5) "x4"
# zrevrangebyscore key max min [withscores] 从大到小的按分数排序
127.0.0.1:6379> ZREVRANGEBYSCORE zset2 5 2 withscores
1) "y5"
2) "5"
3) "x4"
4) "4"
5) "y3"
6) "3"
7) "x2"
8) "2"
# zrevrank key member 按从大到小的顺序,求元素的排行
127.0.0.1:6379> ZREVRANK zset1 x4
(integer) 5
# zscore key member 返回成员的分数值
127.0.0.1:6379> ZSCORE zset1 x5
"5"
# zunionstore desKey numKeys key1 [key2 key3 key4 …] 求多个有序集合的并集,其中 numKeys是有序,集合的个数
127.0.0.1:6379> ZUNIONSTORE des_key 2 zset1 zset2
(integer) 14
# zincrby key increment member 给有序集合成员值为 member 的分数增加 increment
127.0.0.1:6379> ZINCRBY zset1 5 x9
"14"
# zremrangebyscore key start stop 根据分数区间进行删除
127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 2
(integer) 0
# zremrangebyscore key start stop 根据分数区间进行删除
127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 5
(integer) 3
# zremrangebyrank key start stop 按照分数排行从小到大的排序删除,从0开始计算
127.0.0.1:6379> ZREMRANGEBYRANK zset1 1 3
(integer) 3
# zremrangebylex key min max 按照值的分布进行删除
127.0.0.1:6379> ZREMRANGEBYLEX zset2 [y1 [y5
(integer) 6
127.0.0.1:6379>
127.0.0.1:6379> ZCARD zset1
(integer) 3
127.0.0.1:6379> ZCARD zset2
(integer) 3
127.0.0.1:6379>
127.0.0.1:6379> ZRANGE zset1 0 999
1) "x1"
2) "x8"
3) "x9"
127.0.0.1:6379> ZRANGE zset2 0 999
1) "y7"
2) "x8"
3) "y9"
127.0.0.1:6379>
spring-data-redis 对有序集合的封装
在 Spring 中使用 Redis 的有序集合,需要注意的是 Spring 对 Redis 有序集合的元素的值和分数的范围( Range )和限制( Limit)进行了封装。
我们来看下Spring是如何封装的。 先介绍一个主要的接口一一TypedTuple,它不是一个普通的接口,而一个内部接口.
org.springframework . data. redis.core .ZSetOperations 接口的内部接口,它定义了两个方
法
- getValue()是获取值, getScore()是获取分数,但是它只是一个接口,而不是一个实现类
- spring-data-red is 提供了 一个默认的实现类一DefaultTypedTuple
在默认的情况下 Spring 就会把带有分数的有序集合的值和分数封装到这个类中 ,这样就可以通过这个类对象读取对应的值和分数了 .
Spring 不仅对有序集合元素封装,而且对范围也进行了封装,方便使用.它是使用接口 org.springframe.work.data.redis.connection.RedisZSetCommands 下的内部类 Range 进行封装的,它有一个静态的 range()方法,使用它就可以生成一个 Range 对象了,只是要清楚 Range对象的几个方法才行.
// 设置大于等于 min
public Range gte(Object min)
// 设置大于 min
public Range gt(Object min)
// 设置小于等于 max
public Range lte(Object max)
// 设置小于 max
public Range lt(Object max)
这 4 个方法就是最常用的范围方法.
下面看一下limit,它是接口 org.springframework.data.redis.connection.RedisZSetCommands 下的内部类,它是一个简单的 POJO,它存在两个属性
通过属性的名称很容易知道:offset 代表从第几个开始截取,而 count 代表限制返回的总数量。
使用 Spring 操作有序集合
刚才讨论了 spring-data-redis 项目对有序集合的封装,在此基础上这里的演示示例代码在测试代码前,要把 RedisTemplate 的 keySerializer 和 valueSerializer属性都修改为字符串序列化器 StringRedisSerializer
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis/redis.properties" />
<!--2,注意新版本2.3以后,JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码或百度。 -->
<!-- redis连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数 -->
<property name="maxIdle" value="${redis.maxIdle}" />
<!--连接池的最大数据库连接数 -->
<property name="maxTotal" value="${redis.maxTotal}" />
<!--最大建立连接等待时间 -->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟) -->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 -->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
<property name="testOnBorrow" value="true"></property>
<property name="testOnReturn" value="true"></property>
<property name="testWhileIdle" value="true"></property>
</bean>
<!--redis连接工厂 -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<!--IP地址 -->
<property name="hostName" value="${redis.host.ip}"></property>
<!--端口号 -->
<property name="port" value="${redis.port}"></property>
<!--如果Redis设置有密码 -->
<property name="password" value="${redis.password}" />
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"></property>
<property name="usePool" value="true" />
<!--<property name="database" value="0" /> -->
</bean>
<!-- 键值序列化器设置为String 类型 -->
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:defaultSerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="stringRedisSerializer">
</bean>
</beans>
package com.artisan.redis.baseStructure.zset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
public class SpringRedisZSetDemo {
private static final String ZSET1 = "zset1";
private static final String ZSET2 = "zset2";
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/spring-redis-zset.xml");
RedisTemplate redisTemplate = (RedisTemplate) ctx.getBean("redisTemplate");
// 方便测试,清空数据
redisTemplate.delete(ZSET1);
redisTemplate.delete(ZSET2);
// Spring 提供接口 TypedTuple 操作有序集合
Set<TypedTuple> set1 = new HashSet<ZSetOperations.TypedTuple>();
Set<TypedTuple> set2 = new HashSet<ZSetOperations.TypedTuple>();
// 构造数据
// 127.0.0.1:6379>
// # zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9个成员
// >ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9
// (integer) 9
// # zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9个成员
// > ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9
// (integer) 9
int j = 9;
String value1, value2 = null;
double score1, score2 = 0.0;
for (int i = 1; i <= 9; i++) {
// 计算分数和值
score1 = Double.valueOf(i);
value1 = "x" + i;
if (j > 0) {
score2 = Double.valueOf(j);
value2 = j % 2 == 1 ? "y" + j : "x" + j;
j--;
}
// 使用 Spring提供的默认 TypedTuple-DefaultTypedTuple
TypedTuple typedTuplel = new DefaultTypedTuple(value1,score1);
set1.add(typedTuplel);
TypedTuple typedTuple2 = new DefaultTypedTuple(value2,score2);
set2.add(typedTuple2);
}
// 写入redis
redisTemplate.opsForZSet().add(ZSET1, set1);
redisTemplate.opsForZSet().add(ZSET2, set2);
// 统计总数
Long size = redisTemplate.opsForZSet().size(ZSET1);
System.out.println(ZSET1 + "的size为" + size);
// 计分数为 score ,那么下面的方法就是求 3<=score<=6 的元素
Long count = redisTemplate.opsForZSet().count(ZSET1, 3, 6);
System.out.println(ZSET1 + "中3<=score<=6 的count为" + count);
// 从下标一开始截驭 5 个元素,但是不返回分数 , 每一个元素是 String
Set set = redisTemplate.opsForZSet().range(ZSET1, 1, 5);
printSet(set);
// 截取集合所有元素,并且对集合按分数排序,并返回分数 , 每一个元素是 TypedTuple
Set<TypedTuple> typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);
printTypedTuple(typedTuples);
// 将 zsetl 和 zset2 两个集合的交集放入集合 inter_zset
size = redisTemplate.opsForZSet().intersectAndStore(ZSET1, ZSET2, "inter_zset");
System.out.println("inter_zset size:" + size);
// 查看交集inter_zset中的数据
set = redisTemplate.opsForZSet().range("inter_zset", 0, redisTemplate.opsForZSet().size("inter_zset"));
printSet(set);
// 区间
Range range = Range.range();
range.lt("x8");// 小于
range.gt("x1");// 大于
set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);
printSet(set);
range.lte("x8");// 小于等于
range.gte("x1");// 大于等于
set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);
printSet(set);
// 限制返回个数
Limit limit = Limit.limit();
// 限制返回个数
limit.count(4);
// 限制从第2个开始截取
limit.offset(2);
// 求区间内的元素,并限制返回 4 条
set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range, limit);
printSet(set);
// 求排行,排名第 1 返回 0 ,第 2 返回 1
Long rank = redisTemplate.opsForZSet().rank(ZSET1, "x4");
System.out.println("rank=" + rank);
// 删除元素 , 返回删除个数
size = redisTemplate.opsForZSet().remove(ZSET1, "x5", "x6");
System.out.println("remove " + size + " 个元素");
// 按照排行删除从 0 开始算起,这里将删除第排名第 2 和第 3 的元素
size = redisTemplate.opsForZSet().removeRange(ZSET1, 1, 2);
System.out.println("removeRange " + size + " 个元素");
// 获取所有集合的元索和分数 , 以 -1 代表全部元素
typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);
printTypedTuple(typedTuples);
// 删除指定的元素
size = redisTemplate.opsForZSet().remove(ZSET2, "y3", "y5");
System.out.println("remove " + size + " 个元素");
// 给集合中的一个元素的分数加上 11
Double double1 = redisTemplate.opsForZSet().incrementScore(ZSET2, "y1", 11);
printTypedTuple(redisTemplate.opsForZSet().rangeWithScores(ZSET2, 0, redisTemplate.opsForZSet().size(ZSET2)));
// 从大到小排列
typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(ZSET2, 0, 99);
printTypedTuple(typedTuples);
}
@SuppressWarnings("rawtypes")
public static void printTypedTuple(Set<TypedTuple> typedTuples) {
if (typedTuples != null && typedTuples.isEmpty()) {
return;
}
Iterator<TypedTuple> iterator = typedTuples.iterator();
while (iterator.hasNext()) {
TypedTuple typedTuple = iterator.next();
System.out.println("{value =" + typedTuple.getValue() + ", score=" + typedTuple.getScore() + "}");
}
System.out.println("----------------------");
}
@SuppressWarnings("rawtypes")
public static void printSet(Set set) {
if (set != null && set.isEmpty()) {
return;
}
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object val = iterator.next();
System.out.println(val + "\t");
}
System.out.println("----------------------");
}
}
输出
INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@73a8dfcc: startup date [Wed Sep 26 23:26:54 CST 2018]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring/spring-redis-zset.xml]
zset1的size为9
zset1中3<=score<=6 的count为4
x2
x3
x4
x5
x6
----------------------
{value =x1, score=1.0}
{value =x2, score=2.0}
{value =x3, score=3.0}
{value =x4, score=4.0}
{value =x5, score=5.0}
{value =x6, score=6.0}
{value =x7, score=7.0}
{value =x8, score=8.0}
{value =x9, score=9.0}
----------------------
inter_zset size:4
x2
x4
x6
x8
----------------------
x2
x3
x4
x5
x6
x7
----------------------
x1
x2
x3
x4
x5
x6
x7
x8
----------------------
x3
x4
x5
x6
----------------------
rank=3
remove 2 个元素
removeRange 2 个元素
{value =x1, score=1.0}
{value =x4, score=4.0}
{value =x7, score=7.0}
{value =x8, score=8.0}
{value =x9, score=9.0}
----------------------
remove 2 个元素
{value =x2, score=2.0}
{value =x4, score=4.0}
{value =x6, score=6.0}
{value =y7, score=7.0}
{value =x8, score=8.0}
{value =y9, score=9.0}
{value =y1, score=12.0}
----------------------
{value =y1, score=12.0}
{value =y9, score=9.0}
{value =x8, score=8.0}
{value =y7, score=7.0}
{value =x6, score=6.0}
{value =x4, score=4.0}
{value =x2, score=2.0}
----------------------
注意
使用 Spring 提供的 RedisTemplate 去展示多个命令可以学习到如何使用 RedisTemplate 操作 Redis 。 实际工作中并不是那么用的,因为每一 个操作会尝试从连接池里获取 一 个新的 Redis 连接,多个命令应该使用SessionCallback 接口进行操作 。
代码
代码托管到了 https://github.com/yangshangwei/redis_learn