在
Zzutop
项目中很重要的一块的校园话题的热搜排行,这篇文章记录实现的过程。
1、Redis 的 ZSet
--------Set:
--------ZSet:
可以看到ZSet多出一个分数,很明显该分数将用于排序相关。
有序集合的数据结构如图所示:
- 分数是一个浮点数,在 Java 中是使用双精度表示的
- value 也是 String 数据类型,也是一种基于 hash 的存储结构
- 和Set一样,对于每一个元素都是唯一的,但是对于不同元素而言,它的分数可以一样
- 集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是
- 集合中最大的元素数为 (40 多亿个)
- 元素依赖 key 标示它是属于哪个有序集合
2、Redis ZSet 的 spring-data-redis 封装
Spring 对 Redis ZSet 的元素、分数范围以及限制进行了封装。
元素的封装:--------------------
TypedTuple--org.springframework.data.redis.core.ZSetOperations 接口的内部接口:
public interface TypedTuple<V> extends Comparable<ZSetOperations.TypedTuple<V>> {
@Nullable
V getValue();
@Nullable
Double getScore();
static <V> ZSetOperations.TypedTuple<V> of(V value, @Nullable Double score) {
return new DefaultTypedTuple(value, score);
}
}
TypedTuple 中 getValue() 是获取值,而 getScore() 是获取分数,但是它只是一个接口,而不是一个实现类。spring-data-redis 提供了一个默认的实现类--DefaultTypedTuple,同样它会实现 TypedTuple 接口,在默认的情况下 Spring 就会把带有分数的有序集合的值和分数封装到这个类中,这样就可以通过这个类对象读取对应的值和分数了。
范围的封装:--------------------
Range--接口org.springframework.data.redis.connection.RedisZSetCommands 下的静态内部类:
public static class Range {
@Nullable
RedisZSetCommands.Range.Boundary min;
@Nullable
RedisZSetCommands.Range.Boundary max;
public Range() {
}
public static RedisZSetCommands.Range range() {
return new RedisZSetCommands.Range();
}
public static RedisZSetCommands.Range unbounded() {
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
range.min = RedisZSetCommands.Range.Boundary.infinite();
range.max = RedisZSetCommands.Range.Boundary.infinite();
return range;
}
public RedisZSetCommands.Range gte(Object min) {
Assert.notNull(min, "Min already set for range.");
this.min = new RedisZSetCommands.Range.Boundary(min, true);
return this;
}
public RedisZSetCommands.Range gt(Object min) {
Assert.notNull(min, "Min already set for range.");
this.min = new RedisZSetCommands.Range.Boundary(min, false);
return this;
}
public RedisZSetCommands.Range lte(Object max) {
Assert.notNull(max, "Max already set for range.");
this.max = new RedisZSetCommands.Range.Boundary(max, true);
return this;
}
public RedisZSetCommands.Range lt(Object max) {
Assert.notNull(max, "Max already set for range.");
this.max = new RedisZSetCommands.Range.Boundary(max, false);
return this;
}
@Nullable
public RedisZSetCommands.Range.Boundary getMin() {
return this.min;
}
@Nullable
public RedisZSetCommands.Range.Boundary getMax() {
return this.max;
}
public static class Boundary {
@Nullable
Object value;
boolean including;
static RedisZSetCommands.Range.Boundary infinite() {
return new RedisZSetCommands.Range.Boundary((Object)null, true);
}
Boundary(@Nullable Object value, boolean including) {
this.value = value;
this.including = including;
}
@Nullable
public Object getValue() {
return this.value;
}
public boolean isIncluding() {
return this.including;
}
}
}
它有一个静态的 range() 方法,使用它就可以生成一个 Range 对象了,只是要清楚 Range 对象的几个方法即可对范围进行控制。
限制的封装:--------------------
Limit--接口 org.springframework.data.redis.connection.RedisZSetCommands 下的内部类:
它是一个简单的 POJO,它存在两个属性,它们的 getter 和 setter 方法,如下面的代码所示:
// ......
public interface RedisZSetCommands {
// ......
public class Limit {
int offset;
int count;
//setter和getter方法
}
//......
}
offset 代表从第几个开始截取,而 count 代表限制返回的总数量
3、项目中该如何应用?
问题:热搜话题实体中不仅包括分数、还包括一系列属性。ZSet 中的 value 是对应这一系列属性么?
这样做:
- 后续查找分数时 value 过长
- 后期会不会导致 value 的重复