首页 > 其他分享 >通俗的理解Bitmap(位图)和RoaringBitmap(压缩位图)

通俗的理解Bitmap(位图)和RoaringBitmap(压缩位图)

时间:2023-12-11 17:55:58浏览次数:30  
标签:return String long Bitmap offset RoaringBitmap 通俗 public

一、使用的场景

日常业务中需要大量存储一些重复的字符串,例如每日签到用户、朋友圈点赞的好友、计算每日登录用户等。字符串无论长短不仅会浪费大量的存储资源,而且读取查询也耗时耗资源,那有没有一种存储方式对这一类场景进行优化呢。

二、什么原理

1、Bitmap如何解决这个问题

拿每日签到用户的场景举例,首先对用户id进行编号(offset)。

这样我们只需要每天创建一个只存储位的数组,比如2023-12-8日只有zhangsan和zhaoliu进行了登录,我们只需要一个4位的字节数组来进行存储:

 

这样就达到了减少存储的目的。

2、压缩Bitmap是如何做到压缩的

然而现实的场景中,存储的数据并不是连续的,而是稀疏的,而且创建Bitmap的时候,长度必须是最大的offset的长度,例如我们有64个用户,那么创建的位数组就是64位。

假如我们有1万个用户,但今天只有编号9999的用户登录了系统,那我们还是创建一个占用1万个字节的数组,反而增加了内存的消耗。

为了更容易理解这个问题,我们还是以64位Bitmap为例,也就是最大支持存储64个用户,当我们只存储63时:

先创建了长度为64位的数组,只在63这个位置上的存储是有意义的,其他位置都是0,那这种情况是不是可以进行优化呢?

就会创建一个长度为64的数组,只在63这个位置上的存储是有意义的,其他位置都是0,那这种情况是不是可以想办法优化?

这时候我们就把数组拆分成2组,1到32的用户存储到第1组,把33到64的用户存储到第二组。此时我们发现第一组的数组里全是0,那第一组是不是可以不创建?这样我们就用一个32位大小的数组存储了64位的数据。

接下来更进一步,把64位的数组拆成每16个一组,那么我们只需要创建最后一个分组的数组,也就是16位的数组来达到进一步压缩的目的。

 

 

三、怎么模拟实现

代码来简单的模拟实现压缩Bitmap

1、先创建一个类累存储单个的Bitmap

public class ArrayContainer {
    public char[] values;

    public ArrayContainer(int initialCapacity) {
        this.values = new char[initialCapacity];
    }
}

2、实现压缩Bitmap的逻辑

public class CompressedBitmap {
    public static final char Y = 1;
    public static final char N = 0;
    public static final int ARRAY_CONTAINER_SIZE = 16;

    ArrayContainer[] containers;

    public CompressedBitmap(long initialCapacity) {
        int containersSize = (int)(initialCapacity / ARRAY_CONTAINER_SIZE);
        this.containers = new ArrayContainer[containersSize];
    }

    public void add(long offset){
        int shardIndex = getShardIndex(offset);
        ArrayContainer container = containers[shardIndex];
        if(container == null){
            container = new ArrayContainer(ARRAY_CONTAINER_SIZE);
        }

        int shardOffset = getShardOffset(offset);
        container.values[shardOffset] = Y;
        containers[shardIndex] = container;
    }

    public int get(long offset){
        int shardIndex = getShardIndex(offset);
        ArrayContainer container = containers[shardIndex];
        if(container == null){
            return N;
        }

        int shardOffset = getShardOffset(offset);
        if(Y == container.values[shardOffset]){
            return Y;
        }
        return N;
    }

    public int getShardIndex(long offset){
        return (int)((offset - 1) / ARRAY_CONTAINER_SIZE);
    }

    public int getShardOffset(long offset){
        return (int)(offset % ARRAY_CONTAINER_SIZE);
    }

    public static void main(String[] args) {
        System.out.println(2 / ARRAY_CONTAINER_SIZE);
    }
}

3、测试使用

public static void main(String[] args) {
        CompressedBitmap bitmap = new CompressedBitmap(64);
        bitmap.add(63);
        System.out.println(bitmap.get(63));
        System.out.println(bitmap.get(64));
        System.out.println(bitmap.get(2));
 }

四、Redis实现压缩Bitmap存储

Redis本身是支持Bitmap存储的,但是压缩的Bitmap是不支持的。根据上面的原理,同样可以把一个大的Bitmap转成一个个小的Bitmap来达到压缩的目的。

1、包装实体保存压缩位图的信息

public class CompressedBitInfo implements Serializable {

    /**
     * 真实offset
     */
    private long sourceOffset;
    /**
     * 分桶的编号
     */
    private long bucketIndex;
    /**
     * 桶内的offset
     */
    private long bucketOffset;

    public long getSourceOffset() {
        return sourceOffset;
    }

    public void setSourceOffset(long sourceOffset) {
        this.sourceOffset = sourceOffset;
    }

    public long getBucketIndex() {
        return bucketIndex;
    }

    public void setBucketIndex(long bucketIndex) {
        this.bucketIndex = bucketIndex;
    }

    public long getBucketOffset() {
        return bucketOffset;
    }

    public void setBucketOffset(long bucketOffset) {
        this.bucketOffset = bucketOffset;
    }

    @Override
    public String toString() {
        return "CompressedBitInfo{" + "sourceOffset=" + sourceOffset + ", bucketIndex=" + bucketIndex + ", bucketOffset=" + bucketOffset + '}';
    }
}

2、工具类计算每个小桶的Bitmap

public class CompressedBitTookit {
    public static final long DEFAULT_BUCKET_SIZE = 65536L;

    public static CompressedBitInfo getBitInfo(long sourceOffset, long bucketSize) {
        CompressedBitInfo bucketInfo = new CompressedBitInfo();
        bucketInfo.setSourceOffset(sourceOffset);
        long bucketIndex = sourceOffset / bucketSize;
        long bucketOffset = sourceOffset % bucketSize;
        bucketInfo.setBucketIndex(bucketIndex);
        bucketInfo.setBucketOffset(bucketOffset);
        return bucketInfo;
    }
    public static CompressedBitInfo getBitInfo(long sourceOffset) {
        CompressedBitInfo bucketInfo = getBitInfo(sourceOffset, DEFAULT_BUCKET_SIZE);
        return bucketInfo;
    }

    public static long getSourceOffset(long bucketIndex, long bucketSize, long bucketOffset) {
        return bucketIndex * bucketSize + bucketOffset;
    }
    public static long getSourceOffset(long bucketIndex,  long bucketOffset) {
        return getSourceOffset(bucketIndex, DEFAULT_BUCKET_SIZE, bucketOffset);
    }

3、读写Bitmap

public void setCompressedBit(String key, long offset, boolean value, int expireSeconds) {
        CompressedBitInfo bitInfo = CompressedBitTookit.getBitInfo(offset);
        String bitKey = getBitKey(key, bitInfo.getBucketIndex());
        redis.setBit(bitKey, bitInfo.getBucketOffset(),value);
        redis.expire(bitKey, expireSeconds);
    }

    
    public void setCompressedBit(String key, long offset, boolean value) {
        CompressedBitInfo bitInfo = CompressedBitTookit.getBitInfo(offset);
        String bitKey = getBitKey(key, bitInfo.getBucketIndex());
        redis.setBit(bitKey, bitInfo.getBucketOffset(),value);
    }

    
    public void setCompressedBit(String key, long offset) {
        this.setCompressedBit(key, offset, true);
    }


    
    public void remCompressedBit(String key, long offset) {
        this.setCompressedBit(key, offset, false);
    }

    
    public Boolean getCompressedBit(String key, long offset) {
        CompressedBitInfo bitInfo = CompressedBitTookit.getBitInfo(offset);
        String bitKey = getBitKey(key, bitInfo.getBucketIndex());
        log.debug("getCompressedBit with key:{}, offset:{}",bitKey, bitInfo.getBucketOffset());
        return redis.getBit(bitKey, bitInfo.getBucketOffset());
    }

    
    public void deleteAllCompressedBit(String key, long maxOffset) {
        CompressedBitInfo bitInfo = CompressedBitTookit.getBitInfo(maxOffset);
        for (int i = 0; i < bitInfo.getBucketIndex(); i++) {
            String bitKey = getBitKey(key, i);
            redis.expire(bitKey, 0);
        }
    }

    private String getBitKey(String key, long bucketIndex) {
        return new StringBuffer(key).append("_").append(bucketIndex).toString();
    }

标签:return,String,long,Bitmap,offset,RoaringBitmap,通俗,public
From: https://www.cnblogs.com/hp616/p/17895029.html

相关文章

  • 无涯教程-MFC - Bitmap Button函数
    位图按钮在其脸上显示图片或图片和文本,这通常是为了使按钮略显,使用从CButton派生的CBitmapButton类创建位图按钮。这是CBitmapButton类中的方法的列表。Sr.No.Name&描述1AutoLoad将对话框中的按钮与CBitmapButton类的对象相关联,按名称加载位图,并调整按钮的大小以适合位......
  • 常见场景题-Redis的bitmap如何实现签到功能?
    Redis的bitmap实现签到系统?答:主要讲一下Redis原生的bitmap的使用方法,以及如何使用bitmap来实现签到功能先来看一下如何使用redisbitmap的原生命令实现签到功能:签到我们先来设计key:userid:yyyyMM,那么假如usera在2023年10月3日和2023年10月4日签到的话,使用以下命令:se......
  • 通俗易懂的 Axios 拦截器指南:提升前端开发效率的利器
    Axios 提供了一种称为 “拦截器(interceptors)” 的功能,使我们能够在请求或响应被发送或处理之前对它们进行全局处理。拦截器为我们提供了一种简洁而强大的方式来转换请求和响应、进行错误处理、添加认证信息等操作。在本文中,我们将深入探讨如何使用Axios的拦截器,并提供一个实际......
  • 通俗解释部分光学名词
    目录光瞳和光阑点扩散函数PSF和调制传递函数MTF波前Wavefront相位屏惠更斯-菲涅尔原理高斯谢尔模型(GSM)光束偏振移位键控技术(PolSK)光瞳和光阑Pupil:光瞳(pupil)是一个黑色开口,光通过它进入光瞳。你可以把它看作是相机中的光圈,控制着多少光线可以进入镜头。当光线充足时,光瞳......
  • Halcon 与 bitmap 互转
     Halcon与bitmap互转:publicvoidBitmap2HObjectBpp24(Bitmapbmp,outHObjectimage){try{Rectanglerect=newRectangle(0,0,bmp.Width,bmp.Height);BitmapDatasrcBmpData=bmp.......
  • OpenCV Mat和Bitmap的转换
    最常用的方式是:Cv2.ImRead()可以将位图文件转成Mat数据格式Cv2.ImWrite()可以将Mat数据格式保存到位图文件.不通过读写文件作为转换介质的方法:privatevoidtestMatToPicture(){varmat=Cv2.ImRead("D:\\my_workspace\\opencv\\images\\lena.jpg",ImreadModes.Co......
  • 通俗易懂的js原型链
    原型链是js基础比较重要的一个环节;提到原型链有三个比较重要的概念:实例构造函数以及原型对象,其中三者的关系:构造函数new=》创建一个实例;构造函数prototype=》原型对象;同时原型对象constructor=》构造函数;实例__proto__=>原型对象;new运算符是如何工......
  • 无涯教程-Tk - Bitmap部件函数
    位图小部件用于将位图添加到画布。位图小部件的语法如下所示-canvasNamecreatebitmapxyoptionsx和y设置位图的位置-Bitmap-参数下表在下面列出了可用于位图小部件的选项-Sr.No.Syntax&Remark1-anchorposition位图将相对于x和y位置定位。中心默认为默认值,其他......
  • 写写Redis十大类型bitmap的常用命令
    其实这些命令官方上都有,而且可读性很强,还有汉化组翻译的http://redis.cn/commands.html,不过光是练习还是容易忘,写一写博客记录一下bitmap位图,是由0和1状态表现的二进制bit数组,bitmap是由string作为底层数据结构,本质就是一个数组应用场景:用户签到,视频是否播放,是否登录过,钉钉打卡......
  • 轮询机制是什么意思(通俗理解轮询)
    轮询,英文polling。轮询是按照某种算法进行顺序触发,轮询时会保存当前执行后的索引,以便于下次执行时可以拿到开始索引位置,以达到负载均衡的目的。(表述不是太明确,望指正)轮流则是常规意义上的有顺序排列,而轮询则是按照某种算法进行排列。案例供思考1、一艘船漏水了,上面20个人,但是......