前言:bitmap 占用空间小,查询效率高,在一些场景中使用 bitmap 是一个很好的选择。
SETBIT - 设置指定位置的比特值,可以设为 1 或 0
例如 SETBIT key 10 1,将在 key 对应的 bitmap 中第10位设置为 1。
GETBIT - 获取指定位置的比特值
例如 GETBIT key 10,返回 key 对应 bitmap 的第10位的值。
BITCOUNT - 统计比特值为 1 的数量
例如 BITCOUNT key,返回 key 对应 bitmap 中比特值为 1 的数量。
BITPOS - 查找第一个为指定值的比特位
例如 BITPOS key 1,返回 key 对应 bitmap 中,第一个值为 1 的比特位的位置。
BITFIELD - 一次对多个比特位进行操作
例如 BITFIELD key INCRBY i 5 1,将 key 对应 bitmap 的第 i 个比特位增加 5。
BITOP - 对两个或多个 bitmap 执行位操作(AND/OR/XOR/NOT)并存储结果到另一个 key
例如 BITOP AND destkey key1 key2,将 key1 和 key2 做位与运算,结果存储到 destkey。
注意: bitmap 的第几位值是从 0 开始的,类似于数组下标从 0 开始。
思路: 用户 id 作为偏移量,通过用户 id 就可以快速查到用户是否在线。
SETBIT login_status 10086 1
GETBIT login_status 10086
SETBIT login_status 10086 0
思路: 每个用户每天的签到用 1 个 bit 位表示,一年的签到仅需要 365 个 bit 为。一个月最多只有 31 天,只需要 31 个 bit 位即可。
比如统计用户 10086 在 2023 年 9 月份的打卡记录。
SETBIT uid:sign:10086:202309 2 1
GETBIT uid:sign:10086:202309 2
BITCOUNT uid:sign:10086:202309
BITPOS uid:sign:10086:202309 1
public Result sign() {
//获取当前登陆用户
Long id = UserHolder.getUser().getId();
//获取日期
LocalDateTime now = LocalDateTime.now();
//拼接key
String yyyyMM = now.format(DateTimeFormatter.ofPattern("yyyy:MM:"));
String key = USER_SIGN_KEY +yyyyMM+ id;
//获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//写了redis
stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
return Result.ok();
}
主要逻辑是拿到 bitmap 对应的二进制数据,从后往前遍历连续 1 的个数,连续几个就是当月连续签到几天。
@Override
public Result signCount() {
//获取当前登陆用户
Long id = UserHolder.getUser().getId();
//获取日期
LocalDateTime now = LocalDateTime.now();
//拼接key
String yyyyMM = now.format(DateTimeFormatter.ofPattern("yyyy:MM:"));
String key = USER_SIGN_KEY + yyyyMM + id;
//获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//转二进制字符串
String binaryString = getBitsBefore(key, dayOfMonth);
//计算连续签到天数
int count = 0;
for (int i = binaryString.length() - 1; i >= 0; i--) {
if (binaryString.charAt(i) == '1') {
count++;
} else {
break;
}
}
//返回
return Result.ok(count);
}
/**
* 获取指定 key 的二进制字符串
*/
public String getBitsBefore(String key, int index) {
ValueOperations<String, String> ops = this.stringRedisTemplate.opsForValue();
String value = ops.get(key);
if (StringUtils.isBlank(value)) {
return "";
}
//字符串转二进制字符串
String binaryString = new BigInteger(value.getBytes()).toString(2);
if(index > 0 && index < binaryString.length()) {
return binaryString.substring(0, index);
} else {
return binaryString;
}
}
思路: 将某一个具体的天作为 key(day:20230907),用户 id 作为偏移量,使用 BITOP 命令合并多天的 bitmap。
setbit sign:20230901 0 1
setbit sign:20230901 1 1
setbit sign:20230901 2 1
setbit sign:20230902 0 1
setbit sign:20230902 1 1
setbit sign:20230903 1 1
setbit sign:20230903 2 1
bitop and result sign:20230901 sign:20230902 sign:20230903
bitcount result
思路:使用优惠券编号作为 bitmap key,用户 id 作为 offset。发优惠券的时候,先获取 bitmap 中用户是否领过优惠券。因为每人限领一张,领过的人直接返回。
setbit coupon:a 100 1
setbit coupon:a 101 1
getbit coupon:a 100
/**
* 通过 redis 的 bitmap 判断是否是一人一单
*/
private boolean isOnePersonOneOrderByRedis(Long voucherId) {
Long userId = UserHolder.getUser().getId();
String userVoucherKey = "user_voucher:" + voucherId;
//获取该用户在Bitmaps中的状态
Boolean isOrder = stringRedisTemplate.opsForValue().getBit(userVoucherKey, userId);
if (isOrder == null || !isOrder) {
//如果该用户没有购买过该商品,那么在Bitmaps中设置该用户的状态为已购买
stringRedisTemplate.opsForValue().setBit(userVoucherKey, userId, true);
return true;
} else {
//如果该用户已经购买过该商品,那么返回false
return false;
}
}
思路: 使用日期作为 key,然后用户 id 为 offset。
假如 20230901 活跃用户情况是: [1,0,1,1,0]。20230902 活跃用户情况是 :[ 1,1,0,1,0 ]
bitop and dest1 20230901 20230902
bitcount dest1
bitop or dest2 20201009 20201010
bitcount dest2
我是 xiucai,一位后端开发工程师。
如果你对我感兴趣,请移步我的个人博客,进一步了解。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于个人博客,未经许可禁止转载💌