一、Redis的数据类型
Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构(hash)、有序集合(zset)和基数(HyperLogLog):
二、字符串(String)
1、操作基本的字符串
// 设值
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForValue().set("key2", "value2");
// 通过key获取值
String value1 = (String) redisTemplate.opsForValue().get("key1");
System.out.println(value1);
// 通过key删除值
redisTemplate.delete("key1");
// 求长度
Long length = redisTemplate.opsForValue().size("key2");
System.out.println(length);
// 设值新值并返回旧值
String oldValue2 = (String) redisTemplate.opsForValue().getAndSet("key2", "new_value2");
System.out.println(oldValue2);
// 通过key获取值.
String value2 = (String) redisTemplate.opsForValue().get("key2");
System.out.println(value2);
// 求子串
String rangeValue2 = redisTemplate.opsForValue().get("key2", 0, 3);
System.out.println(rangeValue2);
// 追加字符串到末尾,返回新串长度
int newLen = redisTemplate.opsForValue().append("key2", "_app");
System.out.println(newLen);
String appendValue2 = (String) redisTemplate.opsForValue().get("key2");
System.out.println(appendValue2);
注意
:在操作字符串数据时需要注意将redisTemplate中的值的序列化器改为StringRedisSerializer,否则以上操作的结果会有问题
@Bean
public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(jedisConnectionFactory);
template.setKeySerializer(stringRedisSerializer());
template.setValueSerializer(stringRedisSerializer());
return template;
}
在 Spring 中,redisTemplate.opsForValue() 所返回的对象可以操作简单的键值对,可以是字符串,也可以是对象,具体依据你所配置的序列化方案。
2、操作数值字符串
如果字符串是数字(整数或者浮点数),那么 Redis 还能支持简单的运算。不过它的运算能力比较弱,目前版本只能支持简单的加减法运算。
redisTemplate.opsForValue().set("i","9");// 9
redisTemplate.opsForValue().increment("i",1); // 10
// 注意不能直接通过redisTemplate做减法运算
redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("i")); // 9
redisTemplate.getConnectionFactory().getConnection().decrBy(redisTemplate.getKeySerializer().serialize("i"),6); //3
redisTemplate.opsForValue().increment("i",2.3); // 5.3
System.out.println(((String)redisTemplate.opsForValue().get("i")));
Tip
:
1、getConnection() 只是获取一个 spring data redis 项目中封装的底层RedisConnection对象 ,也可以获取原始的连接对象—— Jedis 对象,比如下面这段代码:
Jedis jedis = (Jedis)redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
2、因为 Redis 的版本在更替,支持的命令会有所不一,而 Spring 提供的 RedisTemplate 方法不足以支撑 Redis 的所有命令,而使用纯粹的 Java Redis 的最新 API 则可以看到这些命令对应的方法
3、所有关于减法的方法,原有值都必须是整数,否则就会引发异常
,比如下面:
redisTemplate.opsForValue().set ("i", "8.9");// 浮点数而非整数
redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("i"));
4、3中提到的情况可以通过加上一个负数来实现对浮点数的减法运算
redisTemplate.opsForValue().set("i","8.5");
redisTemplate.opsForValue().increment("i",-3.2);// 加上一个负数
System.out.println(redisTemplate.opsForValue().get("i"));
5、Redis的key不仅支持String类型,也支持其他类型,但是要注意设置键的序列化器(不可为StringRedisSerializer,会报型转化异常,可以设置为JdkSerializationRedisSerializer)
// 数值型的键,注意设置键的序列化器为JdkSerializationRedisSerializer
redisTemplate.opsForValue().set(123,"123test");
String o = (String)redisTemplate.opsForValue().get(123);
System.out.println(o);
三、哈希(Hash)
1、操作命令
Redis 中的哈希结构就如同 Java 的 map 一样,一个对象里面有许多键值对,它是特别适合存储对象的,如果内存足够大,那么一个 Redis 的 hash 结构可以存储 2 的 32 次方减 1 个键值对(40 多亿)。在 Redis 中,hash 是一个 String 类型的 field 和 value 的映射表
,因此我们存储的数据实际在 Redis 内存中都是一个个字符串而已。hash 的键值对在内存中是一种无序的状态,我们可以通过键找到对应的值。
首先,命令都是以 h 开头,代表操作的是 hash 结构。其次,大多数命令多了一个层级 field,这是 hash 结构的一个内部键,也就是说 Redis 需要通过 key 索引到对应的 hash 结构,再通过 field 来确定使用 hash 结构的哪个键值对。
如果哈希结构是个很大的键值对,那么使用它要十分注意,尤其是关于 hkeys、hgetall、hvals 等返回所有哈希结构数据的命令,会造成大量数据的读取。这需要考虑性能和读取数据大小对 JVM 内存的影响。
对于数字的操作命令 hincrby 而言,要求存储的也是整数型的字符串,对于 hincrbyfloat 而言,则要求使用浮点数或者整数,否则命令会失败。
2、Spring操作Hash
配置RedisTemplate
@Bean
public RedisTemplate redisTemplate(){
RedisTemplate redisTemplate = new RedisTemplate();
// 设置默认的序列化器
redisTemplate.setDefaultSerializer(stringRedisSerializer());
redisTemplate.setKeySerializer(stringRedisSerializer());
redisTemplate.setValueSerializer(jdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
这里把 Spring 提供的 RedisTemplate 的默认序列化器(defaultSerializer)修改为了字符串序列化器。因为在 Spring 对 hash 结构的操作中会涉及 map 等其他类的操作,所以需要明确它的规则。这里只是指定默认的序列化器,如果想为 hash 结构指定序列化器,可以使用 RedisTemplate 提供的两个属性 hashKeySerializer 和 hashValueSerializer,来为 hash 结构的 field 和 value 指定序列化器。
String key = "hash";
Map<String, String> map = new HashMap<String,String>();
map.put("f1", "val1");
map.put("f2", "val2");
// 相当于hmset命令
redisTemplate.opsForHash().putAll(key, map);
// 相当于hset命令
redisTemplate.opsForHash().put(key, "f3", "6");
System.out.println(redisTemplate.opsForHash().get(key,"f3"));
// 相当于 hexists key filed 命令
boolean exists = redisTemplate.opsForHash().hasKey(key, "f3");
System.out.println(exists);
// 相当于hgetall命令
Map keyValMap = redisTemplate.opsForHash().entries(key);
//相当于hincrby命令
redisTemplate.opsForHash().increment(key, "f3",2);
System.out.println(redisTemplate.opsForHash().get(key,"f3"));
//相当于hincrbyfloat命令
redisTemplate.opsForHash().increment (key, "f3", 0.88);
System.out.println(redisTemplate.opsForHash().get(key,"f3"));
//相当于hvals命令
List valueList = redisTemplate.opsForHash().values(key);
//相当于hkeys命令
Set keyList = redisTemplate.opsForHash().keys(key);
List<String> fieldList = new ArrayList<String>();
fieldList.add("f1");
fieldList.add("f2");
//相当于hmget命令
List valueList2 = redisTemplate.opsForHash().multiGet(key, keyList);
//相当于hsetnx命令
boolean success = redisTemplate.opsForHash().putIfAbsent(key,"f4","val4");
System.out.println(success);
//相当于hdel命令
Long result = redisTemplate.opsForHash().delete(key, "fl", "f2");
System.out.println(result);
注意:
1)hmset 命令,在 Java 的 API 中,是使用 map 保存多个键值对在先的。在 Spring 中使用 redisTemplate.opsForHash().putAll(key,map) 方法相当于执行了 hmset 命令,使用了 map,由于配置了默认的序列化器为字符串,所以它也只会用字符串进行转化,这样才能执行对应的数值加法,如果使用其他序列化器,则后面的命令可能会抛出异常。
2)hgetall 命令会返回所有的键值对,并保存到一个 map 对象中,如果 hash 结构很大,那么要考虑它对 JVM 的内存影响。
3)hincrby 和 hincrbyFloat 命令都采用 increment 方法,Spring 会识别它具体使用何种方法。
4)redisTemplate.opsForHash().values(key) 方法相当于 hvals 命令,它会返回所有的值,并保存到一个 List 对象中;而 redisTemplate.opsForHash().keys(key) 方法相当于 hkeys 命令,它会获取所有的键,保存到一个 Set 对象中。
5)在使用大的 hash 结构时,需要考虑返回数据的大小,以避免返回太多的数据,引发 JVM 内存溢出或者 Redis 的性能问题。
四、链表(List)
1、结构和命令
链表结构是 Redis 中一个常用的结构,它可以存储多个字符串,而且它是有序的,能够存储 2 的 32 次方减 1
个节点(超过 40 亿个节点)。Redis 链表是双向的,因此即可以从左到右,也可以从右到左遍历它存储的节点。
由于是双向链表,所以只能够从左到右,或者从右到左地访问和操作链表里面的数据节点。但是使用链表结构就意味着读性能的丧失,所以要在大量数据中找到一个节点的操作性能是不佳的,因为链表只能从一个方向去遍历得到所要节点。
链表结构的优势在于插入和删除的便利,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而已,无需移动元素。
由此可见,链表结构的使用是需要注意场景的,对于那些经常需要对数据进行插入和删除的列表数据使用它是十分方便的,因为它可以在不移动其他节点的情况下完成插入和删除。而对于需要经常查找的,使用它性能并不佳,它只能从左到右或者从右到左的查找和比对。
因为是双向链表结构,所以 Redis 链表命令分为左操作和右操作两种命令,左操作就意味着是从左到右,右操作就意味着是从右到左。
Tip
:
①其中以“l”开头的代表左操作,以“r”开头的代表右操作。对于很多个节点同时操作的,需要考虑其花费的时间,因为这将是十分消耗性能的命令,会导致 Redis 服务器的卡顿。对于不允许卡顿的一些服务器,可以进行分批次操作,以避免出现卡顿。
②这些操作链表的命令都是进程不安全的,因为当我们操作这些命令的时候,其他 Redis 的客户端也可能操作同一个链表,这样就会造成并发数据安全和一致性的问题,尤其是当你操作一个数据量不小的链表结构时,常常会遇到这样的问题。
为了解决不安全问题,Redis 提供了链表的阻塞命令,它们在运行的时候,会给链表加锁,以保证操作链表的命令安全性。
当使用这些命令时,Redis 就会对对应的链表加锁,加锁的结果就是其他的进程不能再读取或者写入该链表,只能等待命令结束。加锁的好处可以保证在多线程并发环境中数据的一致性,保证一些重要数据的一致性,比如账户的金额、商品的数量。不过在保证这些的同时也要付出其他线程等待、线程环境切换等代价,这将使得系统的并发能力下降。在实际的项目中,虽然阻塞可以有效地保证数据的一致性,但是阻塞就意味着其他进程的等待,CPU 需要给其他线程挂起、恢复等,更多的时候我们希望的并不是阻塞的处理请求,所以这些命令在实际中使用得并不多。
2、在Spring中操作Redis的链表(List)
①RedisTemplate的配置:和操作hash相同
@Bean
public RedisTemplate redisTemplate(){
RedisTemplate redisTemplate = new RedisTemplate();
// 设置默认的序列化器
redisTemplate.setDefaultSerializer(stringRedisSerializer());
redisTemplate.setKeySerializer(stringRedisSerializer());
redisTemplate.setValueSerializer(jdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
②具体操作
try {
//删除链表,以便我们可以反复测试
redisTemplate.delete("list");
//把node3插入链表list
redisTemplate.opsForList().leftPush("list", "node3");
List<String> nodeList = new ArrayList<String>();
for (int i = 2; i >= 1; i--) {
nodeList.add("nnode" + i);
}
//相当于lpush把多个价值从左插入链表
redisTemplate.opsForList().leftPushAll("list", nodeList);
//从右边插入一个节点
redisTemplate.opsForList().rightPush("list", "node4");
//获取下标为0的节点
String nodel = (String) redisTemplate.opsForList().index("list", 0);
//获取链表长度
long size = redisTemplate.opsForList().size("list");
//从左边弹出一个节点
String lpop = (String) redisTemplate.opsForList().leftPop("list");
//从右边弹出一个节点
String rpop = (String) redisTemplate.opsForList().rightPop("list");
//注意,需要使用更为底层的命令才能操作linsert命令
//使用linsert命令在node2前插入一个节点
redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"), RedisListCommands.Position.BEFORE, "node2".getBytes("utf-8"), "before_node".getBytes("utf-8"));
//使用linsert命令在node2后插入一个节点
redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"), RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));
//判断list是否存在,如果存在则从左边插入head节点
redisTemplate.opsForList().leftPushIfPresent("list", "head");
//判断list是否存在,如果存在则从右边插入end节点
redisTemplate.opsForList().rightPushIfPresent("list", "end");
//从左到右,或者下标从0到10的节点元素
List valueList = redisTemplate.opsForList().range("list", 0, 10);
nodeList.clear();
for (int i = 1; i <= 3; i++) {
nodeList.add("node");
}
//在链表左边插入三个值为node的节点
redisTemplate.opsForList().leftPushAll("list", nodeList);
//从左到右删除至多三个node节点
redisTemplate.opsForList().remove("list", 3, "node");
//给链表下标为0的节点设置新值
redisTemplate.opsForList().set("list", 0, "new_head_value");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
Tip
:在多值操作的时候,往往会使用 list 进行封装,比如 leftPushAll 方法,对于很大的 list 的操作需要注意性能,比如 remove 这样的操作,在大的链表中会消耗 Redis 系统很多的性能。
③阻塞操作
// 清空数据,可以重复测试
redisTemplate.delete ("list1");
redisTemplate.delete ("list2");
//初始化链表 list1
List<String> nodeList = new ArrayList<String>();
for (int i=1; i<=5; i++) {
nodeList.add("node" + i);
}
redisTemplate.opsForList().leftPushAll("list1", nodeList);
// Spring 使用参数超时时间作为阻塞命令区分,等价于 blpop 命令,并且可以设置时间参数 redisTemplate.opsForList().leftPop ("list1", 1, TimeUnit.SECONDS);
// Spring 使用参数超时时间作为阻塞命令区分,等价于 brpop 命令,并且可以设置时间参数
redisTemplate.opsForList().rightPop("list1", 1, TimeUnit.SECONDS);
nodeList.clear();
// 初始化链表 list2
for (int i=1; i<=3; i++) {
nodeList.add("dato" + i);
}
redisTemplate.opsForList().leftPushAll("list2", nodeList);
// 相当于 rpoplpush 命令,弹出 list1 最右边的节点,插入到 list2 最左边
redisTemplate.opsForList().rightPopAndLeftPush("list1","list2");
// 相当于brpoplpush命令,注意在 Spring 中使用超时参数区分 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2",1,TimeUnit.SECONDS);
Tip
:在 Spring 中它和非阻塞命令的方法是一致的,只是它会通过超时参数进行区分
,而且我们还可以通过方法设置时间的单位,使用还是相当简单的。注意,它是阻塞的命令,在多线程的环境中,它能在一定程度上保证数据的一致但是性能却不佳。
五、集合(Set)
1、结构和命令
Redis 的集合不是一个线性结构,而是一个哈希表结构,它的内部会根据 hash 值来存储和查找数据,理论上一个集合可以存储 2 的 32 次方减 1 个节点(大约 42 亿)个元素,因为采用哈希表结构,所以对于 Redis 集合的插入、删除和查找的复杂度都是 o(1),只是我们需要注意 3 点:
①对于集合而言,它的每一个元素都是不能重复的,当插入相同元素的时候都会失败
②集合是无序的
③集合的每一个元素都是 String 数据结构类型
Redis 可以对不同的集合进行操作,比如求出两个或者以上的集合的交集、差集和并集等。
命令的前缀都包含了一个 s,用来表达这是集合的命令,集合是无序的,并且支持并集、交集和差集的运算。
注意
:
①sdiff set1 set2:并非真正意义上的差集,而是set1中有但set2中没有的元素,不包含set2中有但set1中没有的元素,要想求真正的差集需要再计算sdiff set2 set1,然后求这两个结果的并集
2、在Spring中操作集合(Set)
Set set = null;
// 将元素加入列表
redisTemplate.boundSetOps("set1").add("vl", "v2", "v3", "v4", "v5", "v6");
redisTemplate.boundSetOps("set2").add("v0", "v2", "v4", "v6", "v8");
//求集合长度
redisTemplate.opsForSet().size("set1");
//求差集
set = redisTemplate.opsForSet().difference("set1", "set2");
//求交集
set = redisTemplate.opsForSet().intersect("set1", "set2");
//判断是否集合中的元素
boolean exists = redisTemplate.opsForSet().isMember("set1", "vl");
//获取集合所有元素
set = redisTemplate.opsForSet().members("set1");
//从集合中随机弹出一个元素
String val = (String) redisTemplate.opsForSet().pop("set1");
//随机获取一个集合的元素
val = (String) redisTemplate.opsForSet().randomMember("set1");
//随机获取2个集合的元素
List list = redisTemplate.opsForSet().randomMembers("set1", 2L);
//删除一个集合的元素,参数可以是多个
redisTemplate.opsForSet().remove("setl", "v1");
//求两个集合的并集
redisTemplate.opsForSet().union("set1", "set2");
//求两个集合的差集,并保存到集合diff_set中
redisTemplate.opsForSet().differenceAndStore("set1", "set2", "diff_set");
//求两个集合的交集,并保存到集合inter_set中
redisTemplate.opsForSet().intersectAndStore("set1", "set2", "inter_set");
//求两个集合的并集,并保存到集合union_set中
redisTemplate.opsForSet().unionAndStore("set1", "set2", "union_set");
六、有序集合(ZSet)
1、结构和命令
有序集合和集合类似,只是说它是有序的,和无序集合的主要区别在于每一个元素除了值之外,它还会多一个分数。分数是一个浮点数,在 Java 中是使用双精度表示的,根据分数,Redis 就可以支持对元素从小到大或者从大到小的排序。这里和无序集合一样,对于每一个元素都是唯一的,但是对于不同元素而言,它的分数可以一样。元素也是 String 数据类型,也是一种基于 hash 的存储结构。
有序集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是o(1)。有序集合中最大的成员数为 2 的 32 次方减 1(40 多亿个成员)。
有序集合是根据 key 标示它是属于哪个集合,根据分数进行排序,所以值和分数都是必须的,而实际上不仅可以对分数进行排序,在满足一定的条件下,也可以对值进行排序。
Tip
:在对有序集合、下标、区间的表示方法进行操作的时候,需要十分小心命令,注意它是操作分数还是值,稍有不慎就会出现问题。
2、在Spring中操作有序集合(ZSet)
Spring 对 Redis 有序集合的元素的值和分数的范围(Range)和限制(Limit)进行了封装,在Spring Data Redis中有一个接口——TypedTuple,它不是一个普通的接口,而是一个内部接口,它是 org.springframework.data.redis.core.ZSetOperations 接口的内部接口,它定义了两个方法:
public interface ZSetOperations<K,V>{
......
public interface TypedTuple<V> extends Comparable<TypedTuple<V>< {
V getValue();
Double getScore();
}
......
}
这里的 getValue() 是获取值,而 getScore() 是获取分数,但是它只是一个接口,而不是一个实现类。spring-data-redis 提供了一个默认的实现类—— DefaultTypedTuple,同样它会实现 TypedTuple 接口,在默认情况下 Spring 会把带有分数的有序集合的值和分数封装到这个类中,这样就可以通过这个类对象读取对应的值和分数了。
Spring 不仅对有序集合元素封装,而且对范围也进行了封装。它是使用接口 org.springframework.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)
Spring也提供了对限制的封装,它是接口 org.springframework.data.redis.connection.RedisZSetCommands 下的内部类,它是一个简单的 POJO,它存在两个属性offset和count,offset 代表从第几个开始截取,而 count 代表限制返回的总数量:
public interface RedisZSetCommands {
// ......
public class Limit {
int offset;
int count;
//setter和getter方法
}
//......
}
①设置RedisTemplate的键和值的序列化器为StringRedisSerializer
@Bean
public RedisTemplate redisTemplate(){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setDefaultSerializer(stringRedisSerializer());
redisTemplate.setKeySerializer(stringRedisSerializer());
redisTemplate.setValueSerializer(stringRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
Tip
:RedisTemplate对象的键和值的序列化器对于我们操作的数据类型有很大的影响,因此在一个项目中有可能需要根据使用的场景实例化仅有序列化器不同的多个RedisTemplate对象。
②操作示例
// Spring提供接口 TypedTuple 操作有序集合
Set<ZSetOperations.TypedTuple> set1 = new HashSet<ZSetOperations.TypedTuple>();
Set<ZSetOperations.TypedTuple> set2 = new HashSet<ZSetOperations.TypedTuple>();
int j = 9;
for (int i = 1; i <= 9; i++) {
j--;
// 计算分数和值
Double score1 = Double.valueOf(i);
String value1 = "x" + i;
Double score2 = Double.valueOf(j);
String value2 = j % 2 == 1 ? "y" + j : "x" + j;
// 使用 Spring 提供的默认 TypedTuple--DefaultTypedTuple
ZSetOperations.TypedTuple typedTuple1 = new DefaultTypedTuple(value1, score1);
set1.add(typedTuple1);
ZSetOperations.TypedTuple typedTuple2 = new DefaultTypedTuple(value2, score2);
set2.add(typedTuple2);
}
// 将元素插入有序集合zset1
redisTemplate.opsForZSet().add("zset1", set1);
redisTemplate.opsForZSet().add("zset2", set2);
// 统计总数
Long size = null;
size = redisTemplate.opsForZSet().zCard("set1");
// 计分数为score,那么下面的方法就是求 3<=score<=6的元素
size = redisTemplate.opsForZSet().count("zset1", 3, 6);
Set set = null;
// 从下标一开始截取5个元素,但是不返回分数,每一个元索是String
set = redisTemplate.opsForZSet().range("zset1", 1, 5);
// 截取集合所有元素,并且对集合按分数排序,并返回分数,每一个元素是TypedTuple
set = redisTemplate.opsForZSet().rangeWithScores("zset1", 0, -1);
// 将zset1和zset2两个集合的交集放入集合inter_zset
size = redisTemplate.opsForZSet().intersectAndStore("zset1", "zset2","inter_zset");
// 区间
RedisZSetCommands.Range range = RedisZSetCommands.Range.range();
range.lt("x8");// 小于
range.gt("x1"); // 大于
set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
range.lte("x8"); // 小于等于
range.gte("xl"); // 大于等于
set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
// 限制返回个数
RedisZSetCommands.Limit limit = RedisZSetCommands.Limit.limit();
// 限制返回个数
limit.count(4);
// 限制从第五个开始截取
limit.offset(5);
// 求区间内的元素,并限制返回4条
set = redisTemplate.opsForZSet().rangeByLex("zset1", range, limit);
// 求排行,排名第1返回0,第2返回1
Long rank = redisTemplate.opsForZSet().rank("zset1", "x4");
// 删除元素,返回删除个数
size = redisTemplate.opsForZSet().remove("zset1", "x5", "x6");
// 按照排行删除从0开始算起,这里将删除第排名第2和第3的元素
size = redisTemplate.opsForZSet().removeRange("zset2", 1, 2);
// 获取所有集合的元素和分数,以-1代表全部元素
set = redisTemplate.opsForZSet().rangeWithScores("zset2", 0, -1);
// 删除指定的元素
size = redisTemplate.opsForZSet().remove("zset2", "y5", "y3");
// 给集合中的一个元素的分数加上11
Double dbl = redisTemplate.opsForZSet().incrementScore("zset1", "x1",11);
redisTemplate.opsForZSet().removeRangeByScore("zset1", 1, 2);
set = redisTemplate.opsForZSet().reverseRangeWithScores("zset2", 1, 10);
打印TypedTuple:
public static void printTypedTuple(Set<TypedTuple> set) {
if (set != null && set.isEmpty()) {
return;
}
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
TypedTuple val = (TypedTuple) iterator.next();
System.err.print("{value = " + val.getValue() + ", score = "
+ val.getScore() + "}\n");
}
}
七、基数(HyperLogLog)
基数是一种算法。举个例子,一本英文著作由数百万个单词组成,你的内存却不足以存储它们。我们先分析一下业务:
英文单词本身是有限的,在这本书的几百万个单词中有许许多多重复单词,扣去重复的单词,这本书中也就是几千到一万多个单词而已,那么内存就足够存储它们了。
比如数字集合 {1,2,5,7,9,1,5,9} 的基数集合为 {1,2,5,7,9} ,那么基数(不重复元素的个数)就是 5,基数的作用是评估大约需要准备多少个存储单元去存储数据
,但是基数的算法一般会存在一定的误差(但一般是可控的)。Redis 对基数数据结构的支持是从版本 2.8.9 开始的。
基数并不是存储元素,存储元素消耗内存空间比较大,而是给某一个有重复元素的数据集合(一般是很大的数据集合)评估需要的空间单元数,所以它没有办法进行存储,在工作中用得不多, Redis 的 HyperLogLog 命令如下:
命令的使用如下:
首先往一个键为 h1 的 HyperLogLog 插入元素,让其计算基数,到了第 5 个命令“pfadd h1 a”的时候,由于在此以前已经添加过,所以返回了 0。它的基数集合是 {a,b,c,d},故而求集合长度为 4;之后再添加了第二个基数,它的基数集合是{a,z},所以在 h1 和 h2 合并为 h3 的时候,它的基数集合为 {a,b,c,d,z},所以求取它的基数就是 5。
在Spring中操作如下:
redisTemplate.opsForHyperLogLog().add("HyperLogLog", "a", "b" , "c", "d", "a");
redisTemplate.opsForHyperLogLog().add("HyperLogLog2", "a");
redisTemplate.opsForHyperLogLog().add("HyperLogLog2", "z");
Long size = redisTemplate.opsForHyperLogLog().size("HyperLogLog");
System.err.println(size);
size = redisTemplate.opsForHyperLogLog().size("HyperLogLog2");
System.err.println(size);
redisTemplate.opsForHyperLogLog().union ("des_key","HyperLogLog","HyperLogLog2");
size = redisTemplate.opsForHyperLogLog().size("des_key");
System.err.println(size);