1. BitMap
假如用户数量为一千万,平均每人每年签到次数为 10 次,则这张表一年的数据量为 1 亿条;
而每签到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共 22 字节的内存,一个月则最多需要 600 多字节,如果是一千万的用户,则数据量非常庞大;
这样统计一个月的字节大小为4字节,大大缩小了内存使用;
2. 签到功能实现
/**
* BitMap实现用户签到
* @return
*/
@Override
public Result sign() {
//1. 获取当前登录用户
Long userId = UserHolder.getUser().getId();
//2. 获取日期
LocalDateTime now = LocalDateTime.now();
//3. 拼接key
String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + format;
//4. 获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//5. 写入Redis SETBIT key offset 1
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok();
}
3. 签到统计
/**
* 统计签到次数
* @return
*/
@Override
public Result signCount() {
//1. 获取当前登录用户
Long userId = UserHolder.getUser().getId();
//2. 获取日期
LocalDateTime now = LocalDateTime.now();
//3. 拼接key
String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = USER_SIGN_KEY + userId + format;
//4. 获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//5. 获取本月截至今天为止的所有签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
List<Long> result = stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
);
if(result == null || result.isEmpty()) {
// 没有任何结果
return Result.ok(0);
}
Long num = result.get(0);
if(num == null || num == 0) {
return Result.ok(0);
}
//6. 循环遍历
int count = 0;
while(true) {
// 让这个数字与1做与运算,得到数字的最后一个bit位
// 判断这个bit位是否为0
if((num & 1) == 0) {
// 如果为0,说明未签到,结束
break;
}else {
// 如果不为0,说明已签到,计数器+1
count++;
}
// 把数字右移一位,抛弃最后一个bit位,继续下一个bit位(右移一位并赋值)
num >>>= 1;
}
return Result.ok(count);
}