zoukankan      html  css  js  c++  java
  • BitMap原理

    经常能够看到有些大厂的面试题里有一些这样的题目:一个10G的文件,里面全部是自然数,一行一个,乱序排列,对其排序。在32位机器上面完成,内存限制为 2G。

    首先来分析一下题目,10G的文件,只有2G内存,显然,不可能一次性把数据放入内存中直接排序。那么,还有什么其他办法呢?遍寻资料,可以发现大致有两种解决方案:

    1、把大文件分成多个小文件,分别排序,到最后合并成一个文件(我暂时还没搞懂这个方法,所以不会描述,有兴趣的看官可以自己去查一下);

    2、另外一种方法就是著名的bitmap算法了。引用一下《编程珠玑》的内容:

    所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。
    如果说了这么多还没明白什么是Bit-map,那么我们来看一个具体的例子,假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复)。那么我们就可以采用Bit-map的方法来达到排序的目的。要表示8个数,我们就只需要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0
    然后遍历这5个元素,首先第一个元素是4,那么就把4对应的位置为1(可以这样操作 p+(i/8)|(0×01<<(i%8)) 当然了这里的操作涉及到Big-ending和Little-ending的情况,这里默认为Big-ending),因为是从零开始的,所以要把第五位置为1。
    然后再处理第二个元素7,将第八位置为1,,接着再处理第三个元素,一直到最后处理完所有的元素,将相应的位置为1。
    然后我们现在遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的。
    其实就是把计数排序用的统计数组的每个单位缩小成bit级别的布尔数组

    这就是Bit-map的基本思想。Bit-map算法利用这种思想处理大量数据的排序、查询以及去重。

    片头提出的问题,这里自然是要用bitmap算法来解决了,下面先来解释一下算法(本期算法用java实现)。

    1、

    32位机器上的自然数一共有4294967296个,如果用一个bit来存放一个整数,1代表存在,0代表不存在,那么把全部自然数存储在内存只要4294967296 / (8 * 1024 * 1024) = 512MB(8bit = 一个字节),而这些自然数存放在文件中,一行一个数字,需要16G的容量。可见,bitmap算法节约了非常多的空间。

    不过在java中,应该没有bit这种数据结构,最小的是byte,byte占8bit,那么我们可以用byte代表8个连续的数字,不过因为byte的范围是127 ~ -128,最高位是符号位,所以可以变通一下,前7位代表8n ~ 8n + 7的数字,8n + 7这个数可以用符号来区分,即>0即含有8n + 7,<0即不含8n + 7(这里其实不一定要用byte来做,用short,int,long来做都一样的,因为我找到第一篇是用byte,所以就先入为主了)。

    talk is cheap,show me the code. -- Linus Torvalds

    2、

    package main.io;
    
    public class BitMap {
        public byte[] bitArr;
        private static final byte mask = 3;
        private static final int maxNum = (1 << mask) - 1;
        private long count = 0;
    
        BitMap() {
            bitArr = new byte[1 << (Integer.SIZE - mask)];
        }
    }
    

    这里的mask代表的是移位数,n >>3 等价于 Math.floor(n / 8), (1 << 3) - 1 = 7 = bin 111(这两个地方先记着,下面会解释)。

    3、

    设置bit的方法,网上能够找到的代码多数是这样实现的:

    bitArr[num >> mask] |= (1 << (num & maxNum));
    

    但是这个方法会有一个逻辑漏洞,就是(1 << (8n + 7) & 7) = 128,128就超出了byte的范围变成-128了,我就是被这个坑了,还好写了一个php的版本来对比debug。。。o(╥﹏╥)o

    这里要区分原来的数值是否为负数,还有设置的数是否为8n + 7。
    我对于位运算不太熟,所以就把负数按位取反进行 | 运算再转回来。

    设置bit的方法:

        public void setBit(int num) {
            var val = bitArr[num >> mask];
            var bit = num & maxNum;
            if (val >= 0 && bit == maxNum) {
                bitArr[num >> mask] = (byte) ~val;
            } else if (val < 0 && bit != maxNum) {
                bitArr[num >> mask] = (byte) ~(~val | (1 << bit));
            } else if (val >= 0 && bit != maxNum) {
                bitArr[num >> mask] |= (1 << bit);
            }
        }
    

    4、

    只要明白了上面的方法,下面的查询和移除的方法也就十分简单了。

        public byte getBit(int num) {
            var val = bitArr[num >> mask];
            var bit = num & maxNum;
            if (bit == maxNum) {
                return bitArr[num >> mask] < 0 ? (byte) 1 : (byte) 0;
            } else if (val < 0 && bit != maxNum) {
                return (byte) (~bitArr[num >> mask] & (1 << (bit)));
            } else {
                return (byte) (bitArr[num >> mask] & (1 << (bit)));
            }
        }
        
        public void delBit(int num) {
            var val = bitArr[num >> mask];
            var bit = num & maxNum;
            if (bit == maxNum) {
                bitArr[num >> mask] = (byte) ~val;
            } else if (val < 0 && bit != maxNum) {
                bitArr[num >> mask] = (byte) ~(~bitArr[num >> mask] ^ (1 << (bit)));
            } else {
                bitArr[num >> mask] = (byte) (bitArr[num >> mask] ^ (1 << (bit)));
            }
        }
    

    最后还有一个统计bitmap存在数字数量的方法:

        public long countDistinctNum() {
            var length = bitArr.length;
            for (int i = 0; i < length; ++i) {
                if (bitArr[i] >= 0) {
                    count += Integer.bitCount(bitArr[i]);
                }else {
                    count += Integer.bitCount(~bitArr[i]) + 1;
                }
            }
            return count;
        }
    

    明白了bitmap的算法原理,接下来就要实战一下,下期来讲一下利用bitmap给海量数据排序的方法。

     


    作者:菜six岁
    链接:https://www.jianshu.com/p/bf9dbbc147ed
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    微人事项目-mybatis-持久层
    通过外键连接多个表
    springioc
    Redis 消息中间件 ServiceStack.Redis 轻量级
    深度数据对接 链接服务器 数据传输
    sqlserver 抓取所有执行语句 SQL语句分析 死锁 抓取
    sqlserver 索引优化 CPU占用过高 执行分析 服务器检查
    sql server 远程备份 bak 删除
    冒泡排序
    多线程 异步 beginInvoke EndInvoke 使用
  • 原文地址:https://www.cnblogs.com/lipengze/p/11555661.html
Copyright © 2011-2022 走看看