在排序中,经常遇到变量相同情况下的排序问题。在MySQL中可以使用 ORDER BY 约束多个字段。但是在redis中,使用sorted set排序时,score只能设置一个变量。这样在score相同时,只能使用字典序了(这个是从文档上看到的,具体没验证)
这就会出现一些问题了,最简单,在游戏排行榜中,我原来是排第一的,突然来了个人,分数和我相同,但是排到我前面了,我成第二了,玩家肯定不干了。但是redis只能使用一个值作为排序条件,这就需要我们在一个值里既能记录原有的分数,还要记录另一个值。
比较常见的是用时间作为第二个排序条件,谁先达到这个分数,谁排在前面。
要实现这个功能,要满足 可逆 ,稳定排序 两个要求。
- 可逆:把数值处理后还能到的原来的值(分数和时间)
- 稳定排序:添加时间后不能影响原来的排序结果,添加的时间只是在分数相同的情况下才起作用
简单的对数值进行加减乘除是没法实现的了,简单的数值加减乘除一是不能恢复原来的数据,二是会影响到原有的排序。
不卖关子了,使用‘或’运算,可以实现这个需求。把两个数值进行或运算,生成一个数。简单说一下原理。
或 运算的规则是在二进制位上,有1 或 运算后还是1,两个都是0 或运算后是 0。 例:1或2 二进制表示 01|10 = 11。但是现在却发现,无法实现数据还原,这就需要移位了。就是两个8位长的数据,或 运算后要成为16位的数据。
还是上个例子:为了简单起见,假设原数据是2位长,先把原来2位长的数据转成4位长,然后左移2位,1就从原来的01变成了0111。2只变成4位,不移位。2从10变成0010。0100|0010 = 0110。这样就能还原数据了。前两位01是原来的1,后两位10就是原来的2。
下面通过代码来实现一下。
代码用golang实现
package main
import (
"fmt"
"math/rand"
"sort"
)
func main() {
arr := make([]uint64, 10)
b := rand.Perm(10)
for i := 0; i < 10; i++ {
var a uint32
if i%2 == 0 {
a = 10
} else {
a = 5
}
arr[i] = uint64(a)<<32 | uint64(b[i])
fmt.Println(a, b[i], arr[i])
}
sort.Sort(Myuint64(arr))
fmt.Println("排序后")
for _, v := range arr {
fmt.Println(v, v>>32, uint32(v))
}
}
type Myuint64 []uint64
func (this Myuint64) Len() int {
return len(this)
}
func (this Myuint64) Less(i, j int) bool {
return this[i] > this[j]
}
func (this Myuint64) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
简单说一下代码,第一排序条件用了间隔使用了5和是这两个数,第二条件随机生成了10个不重复的随机数,不用时间戳的原因是,程序运行的时间很短,取到的时间戳基本是一个数,看不出区别,本质思想都是一样的。排序使用了go自带的排序接口,不了解的看一下上篇文章,传送门代码中fmt.Println(v, v>>32, uint32(v))
就是解数据,或 运算后,前32位是一个数,后32位是另一个数据。取前面的数据只需要把计算后的数据右移32位就行了,取后面的数据,更简单了,直接强转成32位的类型就行了。
看一下运行结果:
可以看到,排序前,10和5交替出现,第二条件没有规律
排序后,先使用第一个数排序,第一个数相同时,使用第二条件排序。而且我们也成功还原了原来的数据。
好了,这样就实现了用一个数值实现第二条件排序了。在存入redis时只需要把计算后的数据存入redis就行了,显示数据的时候,只需要解一下数据就行了。
好了,到这就介绍完了,有问题欢迎评论区讨论
有疑问加站长微信联系(非本文作者)