BitMap核心思想与应用

BitMap 基本思想

一个 int 在计算机中占 4 个字节。1 个字节占 8bit,也就是说一个 int 数字占 32bit。

而对于某些场景而言。这属于一种巨大的浪费,因为我们可以用对应的 32bit 位对应存储十进制的 0-31 个数,而这就是 BitMap 的基本思想。Bit-map 算法利用这种思想处理大量数据的排序、查询以及去重。

BitMap 代码实现

在讲代码之前我们先补充一些基础关于位运算的基础知识。基础还不错的同学可以跳过这部分。

int 在计算机中的存储方式

int t=1 那么 t 在计算机中如何存储的呢?

0000 0000 0000 0000 0000 0000 0000 0001

为什么要讲这个呢。因为在 BItMap 中涉及到关于 bit 的存储和计算。

运算符基础

左移 << :8 << 2 = > 8*4=32

      8: 0000 0000 0000 0000 0000 0000 0000 1000 
    <<2: 0000 0000 0000 0000 0000 0000 0010 0000    => 2^5= 2*2*2*2*2=32

右移 >>:8 >> : 8 / 4 = 2

      8: 0000 0000 0000 0000 0000 0000 0000 1000
    >>2: 0000 0000 0000 0000 0000 0000 0000 0010      => 2^1=2

8 / 4 => 8 >> 2 8*4 => 8 << 2

  • 位与 &:同位上的两个数都是 1 则位 1,否则为 0
  • 位或 |:同为上的两个数只要有一个为 1 则为 1,否则为 0

OK,位运算的简单回顾就到这里,还有不懂的同学可以自行百度一下。

位图(BitMap)

通过以上知识我们可以知道 一个 int 占 32 个 bit 位。假如我们用这个 32 个 bit 位的每一位的值来表示一个数的话是不是就可以表示 32 个数字,也就是说 32 个数字只需要一个 int 所占的空间大小就可以了,瞬间就可以缩小空间 32 倍。

不过,位图有什么用呢?

有大用处哦,比如,我们要统计某个用户一年的活跃度,就可以使用位图来实现。

一年有 365 天,一个 long 类型可以表示 64 位,365/64=6,只需要 6 个 long 类型就可以记录一个用户一年的活跃情况,怎么记录呢?

很简单,初始时,位图中所有位都是 0,当这个用户某天登录了,就在位图中找到这天,把其位变成 1,一年下来,这张位图就记录了这个用户哪些天登录了,统计这个位图中 1 的数量,除以 365,就得到了他的活跃度。

BitMap 算法原理

假设我们要存储的数字是 64。那么我们要如何申请内存呢? 只要申请 64/32+1 个数组即可。即最大的数 64:bit[64/32+1]=bit[3];

bits[0]:0000 0000 0000 0000 0000 0000 0000 0000   0~31
bits[1]:0000 0000 0000 0000 0000 0000 0000 0000   32~63
bits[2]:0000 0000 0000 0000 0000 0000 0000 0000   64~95

-4/32=0 说明在 bit[0]中,下标 4%32=4 说明在第 5 个位置(下标从 0 开始)

  • 65/32=2 说明在 bit[2]中,下标 65%32=1 说明在 bit[2]的第 2 个位置。

以下是代码实现

public class BitMap {

    byte[] bits;
    /**
     * 初始化要存储的最大的那个数
     */
    int max;
    /**
     * 初始化 bit 数组
     */
    public BitMap(int max) {
        this.max=max;
        bits=new byte[(max>>3)+1];
    }
    /** 添加方法 */
    public void add(int n){
        // 向右移 3 位
        int bitsIndex=n>>3;
        // 取余
        int loc=n%8;
        // 将此下标的数组左移 loc 位置为 1
        bits[bitsIndex]|=1<<loc;
    }

    public void delete(int n){
        int bitsIndex=n>>3;
        int loc=n%8;
        // 先看是是否有该数。
        if (find(n)){
            // 将该位数字减一
            bits[bitsIndex]-=1<<loc;
        }

    }

    public Boolean find(int n){
        int bitsIndex=n>>3;
        int loc=n%8;
        int flag=bits[bitsIndex]&(1<<loc);
        return flag!=0;
    }

    public static void main(String[] args) {
       BitMap bitMap=new BitMap(64);
        bitMap.add(30);
        bitMap.add(28);
        bitMap.add(59);
        bitMap.add(23);
        bitMap.delete(30);
        System.out.println(bitMap.find(28));
        System.out.println(bitMap.find(30));
        System.out.println(bitMap.find(40));


    }

BitMap 应用之快速排序

假设我们要对 0-7 内的 5 个元素(4,7,2,5,3)排序(这里假设这些元素没有重复),我们就可以采用 Bit-map 的方法来达到排序的目的。要表示 8 个数,我们就只需要 8 个 Bit(1Bytes),首先我们开辟 1Byte 的空间,将这些空间的所有 Bit 位都置为 0,

    0000 0000

对应位设置为 1:

    0011 1101

遍历一遍 Bit 区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的,时间复杂度 O(n)。

  • 优点: 运算效率高,不需要进行比较和移位;占用内存少,比如 N=10000000;只需占用内存为 N/8=1250000Byte=1.25M。
  • 缺点:所有的数据不能重复。即不可对重复的数据进行排序和查找。

BitMap 应用之快速去重

2.5 亿个整数中找出不重复的整数的个数,内存空间不足以容纳这 2.5 亿个整数。

首先,根据“内存空间不足以容纳这 2.5 亿个整数”我们可以快速的联想到 Bit-map。下边关键的问题就是怎么设计我们的 Bit-map 来表示这 2.5 亿个数字的状态了。

其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。

因此,我们只需要 2bits 就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为 00,存在一次 01,存在两次及其以上为 11。

那我们大概需要存储空间几十兆左右。 接下来的任务就是遍历一次这 2.5 亿个数字,如果对应的状态位为 00,则将其变为 01; 如果对应的状态位为 01,则将其变为 11;如果为 11,,对应的转态位保持不变。

最后,我们将状态位为 01 的进行统计,就得到了不重复的数字个数,时间复杂度为 O(n)。

BitMap 应用之快速查询

同样,我们利用 Bit-map 也可以进行快速查询,这种情况下对于一个数字只需要一个 bit 位就可以了,0 表示不存在,1 表示存在。假设上述的题目改为,如何快速判断一个数字是够存在于上述的 2.5 亿个数字集合中。 同之前一样,首先我们先对所有的数字进行一次遍历,然后将相应的转态位改为 1。遍历完以后就是查询,由于我们的 Bit-map 采取的是连续存储(整型数组形式,一个数组元素对应 32bits),我们实际上是采用了一种分桶的思想。一个数组元素可以存储 32 个状态位,那将待查询的数字除以 32,定位到对应的数组元素(桶),然后再求余(%32),就可以定位到相应的状态位。如果为 1,则代表改数字存在;否则,该数字不存在。就是我们代码中的 find 方法的实现,只不过我们在代码中是申请的 8 位数组。所以求余(%8)就可以了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘明芳

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

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

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

打赏作者

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

抵扣说明:

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

余额充值