Redis学习笔记之七:有序集合类型
Redis最后一种类型是有序集合类型ZSet,即排序的Set,但又与Set不同的是,它比Set多一个字段分数(score)用于排序等操作,从这点来看,相当于Java中的TreeMap,但与Java的TreeMap不同的是,TreeMap排序是指定Comparator对象来排序,通过比较Key来排序,最后构成一棵树。而Redis的ZSet在存储结构上类似于Set。
1、设值/取值
使用ZADD指令进行设值。格式ZADD key score value1 score value2...,返回值是成功添加元素的个数。演示如下:score必须是字符串形式的数值类型
可以看到score必须是一个有效的float类型。而且score的值是可以重复的,但Key的值不能重复。从这点来看与散列类型很相似,只不过在添加时把Key-Value的顺序颠倒了,而且Value还必须是字符串形式的整数。
ZSCORE指令可以取出对应的值,如上图,取出了Redis中Key为myzset对应的有序集合中Key为b的score。
在Redis中+inf表示正无穷,-inf表示负无穷。
2、排序功能(1)
ZRANGE指令用于获取某一范围的元素并根据score进行从小到大的排序,如果分数一致,则会比较Key,按照字典排序。这个指令与LRANGE指令是一致的。
其中withscores表示是否显示分数,带上这个参数则单行为Key,双行为score。
ZREVRANGE与ZRANGE指令一致,可以看成reverse ZRANGE的缩写,所以这个指令是从大到小。
3、获取有序集合中元素的个数
指令ZCARD用于获取有序集合中元素的个数,想到了Set的SCARD吧,所以从指令就可以看出这两种数据类型很类似。
4、获取指定分数范围内元素的个数
ZCOUNT指令用于获取分数在某个范围内的元素个数,格式ZCOUNT zset minScore maxScore。
可以看到如果minScore大于maxScore是返回0的。
5、删除元素
ZREM指令用于删除元素,格式ZREM zset key1 key2.返回值为实际删除元素个数。
6、按照分数范围删除元素
ZREMRANGEBYSCORE指令用于删除某个分数范围的元素。
格式ZREMRANGEBYSCORE zset minScore maxScore.
可以看到如果minScore大于maxScore,则指令不会删除任何元素。
7、按照分数排名范围删除元素
ZREMRANGEBYRAMK指令用于删除分数从小打大排列的前几位元素。
格式ZREMRANGEBYRANK zset start stop。支持负索引,演示如下:
8、获取元素的排名
ZRANK指令用于获取分数从小到大元素的排名,而ZREVRANK则相反。
Redis的索引是从0开始的。
9、获取指定分数范围的元素。
ZRANGEBYSCORE指令用于获取指定分数范围的前多少个元素。
格式ZRANGEBYSCORE zset minScore maxScore [WITHSCORES] [limit offset count],演示如下:
获取分数从小到大排序,分数在[20 - 80]之间的元素,然后输出分数,并且只取索引从1开始的3个元素(索引从0开始)。
注意,ZRANGE与ZREVRANGE是取分数排序后相应索引位置的数据,0代表第一个,-1代表最右边的元素(负索引),没有像这个命令需要指定分数范围。但ZRANGE zset 0 -1 可以认为与ZRANGEBYSCORE zset -inf +inf limit 0 -1等同。同样minScore大于maxScore返回empty。如下图演示:
与之相对应的是ZREVRANGEBYSCORE指令,格式略有不同
ZREVRANGEBYSCORE zset maxScore minScore [withscores] [limit offset count],演示如下
这儿特别要注意的是,ZRANGE和ZREVRANGE指令后面跟的是索引,且是上包含[]关系,如ZRANGE zset 0 3,是取索引从0到3的元素,包括0和3.而ZRANGEBYSCORE后面是limit,例如ZRANGEBYSCORE .... limit 0 3, 表示的是从索引为0的位置开始,一共取3个元素,如果count为负数(这儿举的例子是3),则相当于从该索引后的所有元素都会被取出来。例子如下:
10、增加某个值的分数
ZINCRBY指令用于增加某个Key对应的Score,指令格式ZINCRBY zset score key。如果Key不存在则相当于zadd指令。如果在Redis中存储文章或者新闻的访问量,没访问一次访问量+1,那这个命令非常有用。演示如下:
11、有序集合之间的运算
指令ZINTERSTORE用于计算有序集合的交集并保存在目标集合内。
ZINTERSTORE desSet numberSets zset1 zset2 zset3 [WIGHTS weight1 weight2] [aggregate SUM|MIN|MAX]
会有numberSets个有序集合参与计算,会依次将zset1, zset2,zset3进行交集运算,参考Set的SINTER指令,然后将所得交集存放进desSet中,那所得的分数怎么办呢?后面的WEIGHTS权重会依次与前面的zset对应,然后zset的分数会乘以权重得到分数就是这个分数,然后根据SUM|MIN|MAX采取相应的操作,演示如下:
此外还有ZUNIONSTORE指令求并集操作。
Redis的Java SDK
测试代码如下(略长):
package org.yamikaze.redis.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.params.sortedset.ZAddParams;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
/**
* 使用Redis的Java SDK测试Zset类型
* @author yamikaze
*/
public class ZSetTest {
private Jedis client;
private static final String KEY = "myzset";
private String key1 = "key1";
private double score1 = 20;
private Map<String, Double> map;
@Before
public void setUp() {
client = MyJedisFactory.defaultJedis();
map = new HashMap<String, Double>(16);
map.put(key1, score1);
map.put("key2", 20d);
map.put("key3", 30d);
map.put("key4", 40d);
map.put("key5", 50d);
map.put("key6", 60d);
}
/**
* 测试ZADD,返回值是插入有序集合元素的个数
* 由于SDK中含有ZAddParams对象参数。
* 这个参数会改变zadd指令的行为,且一旦绑定了ZAddParams对象就不能再绑定
* 所以分为4个测试方法测试ZADD
*/
@Test
public void testZAddWithoutZAddParams() {
long size = client.zadd(KEY, 20, key1);
assertTrue(size == 1);
//有相同的Key时不会做插入,会做更新
size = client.zadd(KEY, 30, key1);
assertTrue(size == 0);
//插入多个元素
size = client.zadd(KEY, map);
assertTrue(size == map.size() -1);
}
/**
* 这儿顺带将ZSCORE指令测试了
*/
@Test
public void testZAddWithZAddParams01() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//指定参数ZAddParams, 经过上面的运行key1对应的score为20
ZAddParams zp = ZAddParams.zAddParams();
//nx表示如果key1不存在则插入,所以下面score为20,没有变为30
zp.nx();
client.zadd(KEY, 30, key1, zp);
double score = client.zscore(KEY, key1);
assertTrue(score == 20);
}
@Test
public void testZAddWithZAddParams02() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//指定参数ZAddParams, 经过上面的运行key1对应的score为20
ZAddParams zp = ZAddParams.zAddParams();
//xx表示如果key存在才作插入(更新),否则不做插入
zp.xx();
client.zadd(KEY, 30, "1234", zp);
//不存在,为空,表示上面的语句没有做插入
//如果使用语句double score来接收结果,会抛出NPE
//如果是double score声明记得在Test上面加上(expected = NullPointerException.class)
Double score = client.zscore(KEY, "1234");
assertNull(score);
}
@Test
public void testZAddWithZAddParams03() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//指定参数ZAddParams, 经过上面的运行key1对应的score为20
ZAddParams zp = ZAddParams.zAddParams();
//ch表示返回被修改的元素个数
zp.ch();
//score被更新
size = client.zadd(KEY, 50, key1, zp);
assertTrue(size == 1);
//返回0,既没有更新score,也没有插入
size = client.zadd(KEY, 50, key1, zp);
assertTrue(size == 0);
//插入新元素
size = client.zadd(KEY, 50, "12345", zp);
assertTrue(size == 1);
}
/**
* 测试ZCARD指令,返回有序集合的元素数
*/
@Test
public void testZCard() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
Long count = client.zcard(KEY);
assertTrue(count.equals(size));
}
/**
* 测试ZCOUNT指令
* 用于返回某个分数范围内的元素个数
* 20 20 30 40 50 60(map中的分数)
*/
@Test
public void testZCount() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//20~50之间的个数应该是map的size()-1, 要包括20与50
long count = client.zcount(KEY, 20d, 50d);
assertTrue(count == map.size() -1);
}
/**
* 测试ZRANGE与ZREVRANGE
*/
@Test
public void testZRangeAndZRevRange() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
Set<String> zranges = client.zrange(KEY, 0, -1);
Set<String> zrevranges = client.zrevrange(KEY, 0, -1);
assertNotNull(zranges);
assertNotNull(zrevranges);
assertTrue(zranges.size() == zrevranges.size());
for(String key: zranges) {
assertTrue(zrevranges.contains(key));
}
for(String key: zrevranges) {
assertTrue(zranges.contains(key));
}
}
/**
* 测试ZRANGE与ZREVRANGE带有WITHSCORES参数
*/
@Test
public void testZRangeAndZRevRangeWithScores() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//ZRANGE与ZREVRANGE各取一半,map的size为6
Set<Tuple> zranges = client.zrangeWithScores(KEY, 0, 2);
Set<Tuple> zrevranges = client.zrevrangeWithScores(KEY, 0, 2);
assertNotNull(zranges);
assertNotNull(zrevranges);
assertTrue(zranges.size() == zrevranges.size());
assertTrue(zranges.size() + zrevranges.size() == map.size());
Set<String> keys = new HashSet<String>(8);
isEquals(zranges, map, keys);
isEquals(zrevranges, map, keys);
assertTrue(keys.size() == map.size());
}
private void isEquals(Set<Tuple> tuples, Map<String, Double> maps, Set<String> keys) {
for(Tuple tuple : tuples) {
String key = tuple.getElement();
double score = tuple.getScore();
double mapScore = map.get(key);
assertTrue(map.containsKey(key));
assertTrue(score == mapScore);
keys.add(key);
}
}
/**
* 测试ZRANGEBYSCORE和ZRAVRANGEBYSCORE
*/
@Test
public void testZRangeByScore() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//SDK并没有提供带有Limit的方法,只有zrangeByScoreWithScores方法
Set<String> ranges = client.zrangeByScore(KEY, 20, 30);
Set<String> revranges = client.zrevrangeByScore(KEY, 60, 40);
assertNotNull(ranges);
assertNotNull(revranges);
assertTrue(ranges.size() == revranges.size());
for(String str: ranges) {
assertFalse(revranges.contains(str));
}
for(String str: revranges) {
assertFalse(ranges.contains(str));
}
}
/**
* 测试ZREM删除元素
*/
@Test
public void testZRem() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
long remSize = client.zrem(KEY, key1, "abc");
assertTrue(remSize == 1);
}
/**
* 测试ZREMRANGEBYSCORE,按照分数范围删除元素
*/
@Test
public void testZRemRangeByScore() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//20 20 30 40 50 60,执行完以下语句集合中应该只剩下一个元素
long remSize = client.zremrangeByScore(KEY, 20, 50);
assertTrue(remSize == map.size() - 1);
long count = client.zcard(KEY);
assertTrue(count == 1);
}
/**
* 测试ZREMRANGEBYRANK,按照排名删除元素
*/
@Test
public void testZRemRangeByRank() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//删除 20 20 30 集合中还剩40 50 60
long remSize = client.zremrangeByRank(KEY, 0, 2);
//如果集合中的元素本身就不足指定的个数,那么将会全部删除
assertTrue(remSize == 3);
Double score = client.zscore(KEY, key1);
assertNull(score);
//删除最大的几个元素
size = client.zadd(KEY, map);
assertTrue(size == 3);
//从指定的位置开始删除,如果start指定为-1,已经是最后一个元素了,无法删除
remSize = client.zremrangeByRank(KEY, -2, -1);
//如果集合中的元素本身就不足指定的个数,那么将会全部删除
assertTrue(remSize == 2);
score = client.zscore(KEY, "key6");
assertNull(score);
score = client.zscore(KEY, "key5");
assertNull(score);
}
/**
* ZRANK获取元素的排名
*/
@Test
public void testZRank() {
long size = client.zadd(KEY, map);
assertTrue(size == map.size());
//ZRANK,从小到大的排名 从0开始
long rank = client.zrank(KEY, key1);
assertTrue(rank == 0);
//key1 与key2的分数一致,然后按照字典排序,所以key2为1
rank = client.zrank(KEY, "key2");
assertTrue(rank != 0);
assertTrue(rank == 1);
//ZRENRANK 从大到小的排名
rank = client.zrevrank(KEY, key1);
assertTrue(rank == 5);
//不存在的key的排名为null
Long rank1 = client.zrank(KEY, "abc");
assertNull(rank1);
}
/**
* 篇幅原因,运算指令略
*/
@After
public void tearDown() {
if(client != null) {
client.del(KEY);
}
}
}
总结
1)、Redis的ZSet算是Redis 5种Value最高级的一种了,在指令上类似于操纵Value的Map。与Map操纵Key刚好颠倒了。除此之外,特性也与Java的Map一致,可以将ZSet看成java Map,但这个Map类型被固定成了Map<String, Double>类型,可以看到上面的测试代码,添加多个Key-Score时就是用的这种Map形式。
2)、ZSet类型的指令繁多,至少算是5种类型最多的,上面列出的指令并不是ZSet指令的全部。而且有些指令不便于记忆,例如ZRANGEBYSCORE,ZREVRANGEBYSCORE、ZREMRANGEBYSCORE等相似的指令容易混淆。且也无指令得到ZSet的某个Key是否存在,当然你可以使用ZSCORE指令,如果返回为Null,则不存在,同理ZRANK指令也行。
3)、Redis的SDK并没有Redis提供的指令那样强大,例如zrangebyscore指令有limit限制,而在SDK中没有。
最后Redis的各个类型的指令有那么多,但实际上有的指令并不是很常用,例如LRANGE, ZRANGE,SMEMBERS等需要遍历整个列表或集合的指令,因为元素比较多时,执行这些指令相当耗费时间,尤其是LRANGE指令。
参考资料
《Redis入门指南》
Redis学习笔记之七:有序集合类型相关推荐
- 15天玩转redis —— 第六篇 有序集合类型
今天我们说一下Redis中最后一个数据类型 "有序集合类型",回首之前学过的几个数据结构,不知道你会不会由衷感叹,开源的世界真好,写这 些代码的好心人真的要一生平安哈,不管我们想没 ...
- zset获取指定score_Redis学习笔记-07Zset有序集合
有序集合是在集合类型的基础上为集合中的每个元素都关联了一个分数,这使得可以在完成插入,删除和判断元素是否存在等基础上,还能否获得分数最高(或最低的)前N项,或一定分数范围的元素等操作.有序也就是通过这 ...
- Dart学习笔记六:集合类型详解
目录 前言 List Set Map 集合的遍历 forEach map where any every 前言 Dart的集合类型使用感觉跟ES6中的集合差不多,这里整理一下Dart集合的常用属性和方 ...
- Redis常用命令入门5:有序集合类型
有序集合类型 上节我们一起学习了集合类型,感受到了redis的强大.现在我们接着学Redis的最后一个类型--有序集合类型. 有序集合类型,大家从名字上应该就可以知道,实际上就是在集合类型上加了个有序 ...
- redis有序集合类型sort set
redis的数据类型之-有序集合 sort set和set类型一样,也是string类型元素的集合,也没有重复的元素,不同的是sort set每个元素都会关联一个权,通过权值可以有序的获取集合中的元素 ...
- redis的数据结构||1) 字符串类型2) 哈希类型3) 列表类型4) 集合类型 5) 有序集合类型详解
2. 下载安装 1. 官网:https://redis.io 2. 中文网:http://www.redis.net.cn/ 3. 解压直接可以使用: * re ...
- Redis学习笔记——SpringDataRedis的使用
与Spring集成 我需要哪些jar包? <dependency><groupId>org.springframework.data</groupId><ar ...
- Redis学习笔记(B站狂神说)(自己总结方便复习)
Redis学习笔记B站狂神说 redis: 非关系型数据库 一.NoSQL概述 1.为什么要用Nosql 1.单机Mysql的年代 思考一下,这种情况下:整个网站的瓶颈是什么? 1.数据量如果太大,一 ...
- Redis学习笔记-GEO经纬度编码原理地理划分
文章目录 Redis学习笔记-GEO经纬度编码原理&地理划分 1.笔记图 2.GEO 应用场景 3.GEO 数据特点举例 4.GeoHash 的编码方法(二分区间,区间编码) 5.GEO 经纬 ...
最新文章
- java解析vue对象数组,Java数组
- 思科、华为远程登录配置小结
- CefSharp学习笔记
- 卡诺模型(用户需求分析模型)
- 技术人生:故事之八	OFFICE是软件打字机?
- 腾讯云HTTPDNS 将上线微信服务平台!
- Excel技巧 - 换行符用法
- 橘子学ES03之Docker安装ELK+cerebro
- 微信公众号页面分享、禁止分享和显示右上角菜单
- 两种方式读取Json文件 数据
- 支付宝/云闪付个人免签
- 洛谷:P1462 通往奥格瑞玛的道路
- 小明学会画几何图形了,他能根据要求,画出空心矩形。
- 憋个大招!高性能mysql第四版pdf在线阅读
- Linux1G光口网卡状态灯不亮,电脑网卡灯不亮怎么办 电脑网卡灯不亮解决方法介绍【详解】...
- 2017福师大计算机在线作业,17秋福师《计算机辅助设计1(PS)》在线作业一答案...
- 张墨千鸿蒙秘境,连接四界精魂之力 镇魔曲手游鸿蒙境副本展开新篇
- mac os推荐常用软件
- 用Python爬取股票数据,绘制K线和均线并用机器学习预测股价
- # ldaps 与 ldap over TLS 的区别
热门文章
- 编程如何实现使用新建的Revit楼板族,创建楼板构件
- 移动开发作业1:类微信的门户页面框架设计
- 潭州课堂25班:Ph201805201 django 项目 第三十二课 后台站点管理(课堂笔记)
- 深度学习环境搭建(二)之 CUDA Driver 安装
- 掌财社移花接木,使用mysql模拟redis
- 计算机ppt基础知识题库,计算机一级考试试题题库office(2)
- Alertmanager邮箱和企业微信的告警模板分享
- android自动开关流量,android开关gprs流量
- 手机屏幕VGA QVGA HVGA WVGA区别
- zabbix通过snmp监控带外管理ping不可用的问题