【Java项目】bitmap实现B站点赞超过500取消最早的点赞记录的实现思路

文章介绍了如何在微信小程序的论坛功能中,使用Redis的Bitmap数据结构来优化点赞超过限制后的处理。通过Bitmap节省空间并提高效率,结合ArrayList存储点赞时间,实现超过500点赞后自动删除最早记录。同时,展示了SpringBoot项目中封装的Redis服务包代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近在做一个微信小程序的项目,然后这个项目有一个论坛功能,前端哥说希望做到点赞超过一定数量之后,会自动取消超过500后的最早那一批的点赞记录,她说这个思路的实现来自于B站,希望我们也能做一下,所以作为负责优化项目的我,就开始着手思考这个内容。

其实基本可以马上就想到就是数据库的操作,但是想了一想,效率肯定很低,因为点赞这种操作很频繁,虽然说可以让点赞的请求累积到一定数量然后通过MQ异步更新数据库,但是效率依旧有点问题,毕竟磁盘操作效率就是更加的低。
所以我就考虑能不能使用内存上的操作,也就是缓存,比如Redis。
然后我就想到了Bitmap这个数据结构。

简单的介绍一下,bitmap其实就是由一个又一个的bit位组成的,也就是0/1,而刚刚好点赞和没点赞就是0/1就能表示。同时,bitmap的基本单位(额,我是怎么理解的)就是一个又一个的byte,由8个bit组成,那么其实这就非常节省空间了,因此如果使用bitmap,那么在时间和空间上,都有相对于使用数据库更好的效率。
bitmap的简单介绍

思路

那么,上面简单的带过了一下基本思路后,现在来聊一聊到底如何实现比较合理。
bitmap有包含,key,offset,value。
其中key就是找到唯一的bitmap的方式,offset就是某一个索引位,value就是0/1。
同时,由于还得做到删除掉最早期的数据,因此我还得做一个能存储用户给那些文章点赞的时间集合,然后如果超过了设定的点赞上限的大小,就把最早的集合中的数据删掉。
在项目中我是用的是一个ArrayList来作为用户点赞的时间排序,因为其实我们并不需要真的去记录用户是什么时候点赞的,只要知道它最早点赞的那一批数据是那些即可了。

所以,我要做的就是,编写操作bitmap的接口,然后对某个offset上的数据置0/1,offset对应的就是被点赞的文章的id,而key就是用户id,value就是用户是否点赞。
然后我还做了一个存储list的集合,这个list保存着用户点赞的那一批文章的id以及顺序。

同时,我还得做到,当用户初始化论坛的时候,必须直接加载出来所有的,他点赞过的文章。
也就是我需要遍历bitmap,并且传递回来所有的位为1的offset。
第一种思路,就是遍历每一个位,然后加一点优化,也就是使用bitcount方法判断当前段上是否有为1的位,而如果没有,那么我们就不再需要判断这个位了,直接跳过这个段即可。大概代码如下
在这里插入图片描述
不过,我上面已经设定过了一个list,那么我直接返回这个list给前端即可。
如下
在这里插入图片描述

Redis服务包

因为是SpringBoot项目,那么其实直接对RedisTemplagte进行封装即可。


@Component
public class RedisService
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 使用bitmap并且设定某一个位值
     * @param key bitmap缓存的键值
     * @param offset bitmap对应的索引位
     * @param value bitmap对应的值 0/1
     * @return 是否设置成功
     */
    public boolean setBit(final String key,final Long offset,final Boolean value){
        return redisTemplate.opsForValue().setBit(key,offset,value);
    }

    /**
     * 获取bitmap某一个位上的值
     * @param key bitmap缓存的键值
     * @param offset bitmap对应的索引位
     * @return 该位键值是0/1
     */
    public boolean getBit(final String key,final Long offset){
        return redisTemplate.opsForValue().getBit(key,offset);
    }

    /**
     * 返回bitmap的长度
     * @param key bitmap缓存的键值
     * @return 返回bitmap的长度
     */
    public long bitmapSize(String key){
        return redisTemplate.opsForValue().size(key);
    }
    /**
     * 获取某个bitmap上的1的个数
     * @param key bitmap缓存的键值
     * @return 返回1的个数
     */
    public Long bitCount(final String key){
        //Long start = 0L; // 起始位置
        //Long end = -1L; // 结束位置,-1表示计算整个bitmap的长度
        Long count = (Long) redisTemplate.execute((RedisCallback<Long>) connection ->
                connection.bitCount(key.getBytes()));
        return count;
    }
    /**
     * 获取某个bitmap上某一段上的1的个数
     * @param key bitmap缓存的键值
     * @return 返回1的个数
     */
    public Long bitCountRange(final String key,final Long start,final Long end){
        Long length = (Long) redisTemplate.execute((RedisCallback<Long>) con ->
            con.bitCount(key.getBytes(),start,end));
        return length;
    }

    /**
     * 是哦那个bitfield获取连续为1的天数
     * @param buildSignKey bitmap缓存的键值
     * @param limit
     * @param offset
     * @return
     */
    @Deprecated
    public List<Long> bitField(final String buildSignKey,final Integer limit,final Long offset){
        return (List<Long>) redisTemplate.execute((RedisCallback<List<Long>>)con->
                con.bitField(buildSignKey.getBytes(),
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType
                                        .unsigned(limit)).valueAt(offset)));
    }
      /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
 }

代码实现

按照上面的思路,假设我们对userId为22的用户,点赞articleId为1000的文章,那么就有如下代码

 @Test
    public void bitmapCode(){
        //15.4k byte  1024k = 1m
        long userId = 22;
        long articleId = 1000;
        //设定喜欢的文章 设定状态为相反
        boolean bit = redisService.getBit(RedisServiceConstants.USER_LIKE_ARTICLE + userId,
                Long.valueOf(articleId));
        redisService.setBit(RedisServiceConstants.USER_LIKE_ARTICLE + userId
                ,Long.valueOf(articleId),!bit);

        List<Long> likeList = redisService.getCacheList(
                RedisServiceConstants.USER_LIKE_TIME + userId);
        if (bit){
            likeList.remove(articleId);
        }else{
            likeList.add(articleId);
        }
        //去重后放入list
        ArrayList<Long> likeList1 = new ArrayList<>(new HashSet<Long>(likeList));
        redisService.deleteObject(RedisServiceConstants.USER_LIKE_TIME+userId);
        redisService.setCacheList(RedisServiceConstants.USER_LIKE_TIME+userId,
                likeList1);
    }

在这里插入图片描述
发送点赞请求之后,就会出现如下的情况。

在这里插入图片描述
而再一次点击之后,就是删除请求了。
可以发现time的那个键就已经被删除了,也就是取消点赞之后,就会把对应的文章的id删除,而这个用户的点赞记录的bitmap还是存在,不过本来为1的位已经变为了0
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZhangBlossom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值