位图的详细解析以及代码实现

目录

1、位图的概念

2、代码的实现

1)Java代码实现

2)   代码测试

3、位图的应用

4、总结


1、位图的概念

面试题
40 亿个不重复的 无符号整数 ,没排过序。给一个无符号整数,如何快速判断一个数是否在这 40 亿个数中。【腾讯】
1. 遍历,时间复杂度 O(N)
2. 排序 (O(NlogN)) ,利用二分查找 : logN(以2为底)
3. 位图解决
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比
特位来代表数据是否存在的信息,如果二进制比特位为 1 ,代表存在,为 0 代表不存在。比如:
位图的详细解析以及代码实现_第1张图片
位图概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据, 整数,数据无重复 的场景。通常是用来判断某个数据存不存在的。如上例子,10 个整数本应该存放需要 40 个字节,此时用位图只需要 3 个字节。
小技巧: 10 亿个字节大概是 0.9G ,可看做是 1G 10 亿个比特位大概是 119 兆,看做 128

2、代码的实现

1)Java代码实现

import java.util.Arrays;

public class MyBitSet {

    public byte[] elem;
    //记录当前位图当中 存在了多少个有效的数据
    public int usedSize;

    public MyBitSet() {
        this.elem = new byte[1];
    }

    /**
     *
     * @param n 多少位
     *          n = 12
     * 有可能会多给一个字节,但是无所谓。
     */
    public MyBitSet(int n) {
        this.elem = new byte[n/8+1];
    }

    /**
     * 设置某一位为 1
     * @param val
     */
    public void set(int val) {
        if(val < 0) {
            throw new IndexOutOfBoundsException();
        }
        int arrayIndex = val / 8 ;
        //扩容
        if(arrayIndex > elem.length-1) {
            elem = Arrays.copyOf(elem,arrayIndex+1);
        }
        int bitIndex = val % 8;

        elem[arrayIndex] |=  (1 << bitIndex); //不能进行异或

        usedSize++;
    }

    /**
     * 判断当前位  是不是1
     * @param val
     */
    public boolean get(int val) {
        if(val < 0) {
            throw new IndexOutOfBoundsException();
        }
        int arrayIndex = val / 8 ;
        int bitIndex = val % 8;

        if((elem[arrayIndex] & (1 << bitIndex)) != 0) {
            return true;
        }
        return false;
    }

    /**
     * 将对应位置 置为 0
     * @param val
     */
    public void reSet(int val) {
        if(val < 0) {
            throw new IndexOutOfBoundsException();
        }
        int arrayIndex = val / 8 ;
        int bitIndex = val % 8;
        elem[arrayIndex] &= ~(1 << bitIndex);
        usedSize--;
    }

    //当前位图中记录的元素的个数
    public int getUsedSize() {
        return usedSize;
    }

}

注:源码中的BitSet使用的是long[]数组,而我这里自己实现的话使用的是byte[]数组,一个比特位来记录一个数据,一个字节八个比特位。

下面我来解释一下代码:

  • 首先是定义位图和写构造方法,根据需要储存的数据的数量来创建相应大小的字节数组。(我理解的应用场景是不能是相互之间差值巨大的数据集,比如1,40亿,80亿,120亿,这样看只是四个数据,但是因为差值太大,需要扩容很大的空间,空间的利用率缺极低,这样的话我们需要使用其他的方式来解决)。
  • 判断一个数值是否纯在位图中,也就是判断它在数组的指定的位是否为1,我们可以通过将1,也就是00000001进行左移模8余数的位来和数组相应位置的数据进行与操作,同样为1,与后值为1,返回true,也就是存在。
  • 设置一个位为1,也代表记录这个数据存在,可以通过00000001左移后进行或操作,这样既不会改变其他位的值也可以将该位置为1,从代码中也可以看出,其实位图也有去重的功能,也是说,如果数据中有多个数值,那么位图对应的位置也只是置为1。
  • 将对应的位置置为0。我们需要先对00000001进行左移然后取反,再去进行与操作,这样如果想要设置的位置原来是0,操作后还是0,是1的话,操作后就会变为0。

2)   代码测试

import java.util.BitSet;
public class Test {

    public static void main1(String[] args) {
        int[] array = {1,3,2,13,10,3,14,18,3};
        MyBitSet myBitSet = new MyBitSet(18);
        for (int i = 0; i < array.length; i++) {
            myBitSet.set(array[i]);
        }

        for (int i = 0; i < myBitSet.elem.length; i++) {
            for (int j = 0; j < 8; j++) {
                if((myBitSet.elem[i] & (1 << j) ) != 0 ) {
                    System.out.print(i*8+j+" ");
                }
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,10,4,18,130};
        MyBitSet myBitSet = new MyBitSet(18);
        for (int i = 0; i < array.length; i++) {
            myBitSet.set(array[i]);
        }
        System.out.println(myBitSet.getUsedSize());
        System.out.println(myBitSet.get(10));
        myBitSet.reSet(10);
        System.out.println(myBitSet.get(10));
    }
    public static void main2(String[] args) {
        int[] array = {1,2,3,10,4,18,13};
        BitSet bitSet = new BitSet(18);
        for (int i = 0; i < array.length; i++) {
            bitSet.set(array[i]);
        }
        System.out.println(bitSet.get(100));
    }
}

对main函数的运行结果如下:

位图的详细解析以及代码实现_第2张图片

可以看到我们的程序是正常运行的,结果也是正确的。(main1函数可以运行结果可以说明去重的特性,main2函数可以验证取不存在的数据的返回结果)

3、位图的应用

1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记
这里我给定一个具体的题目: 给两个文件,分别有10亿个不重复的无符号的整数,我们只有1G内存,如何找到两个文件交集?

这里我们肯定能想到将两个文件来进行遍历对比,都存在的整数就是交集的元素,但是数据量很大,不管是进行排序,还是直接存储都不可行,所以我们需要一种更高效的储存手段。这里我们可以联想到使用我们刚刚将的位图,我们可以设置两个位图,每个位图512M,假设每一个整数存在一个比特位中,那么每个位图我们可以存42亿的数据,可以满足文件数据量的大小,我们分别将两个文件存入两个位图,进行如下的对比:

位图的详细解析以及代码实现_第3张图片

如果两个位图的对应位同时为1,那么说明这个整数是交集的元素。

4、总结

看到这相信你已经对位图有了一定的理解。总结一下,在处理海量数据,不重复无符号整数时,如果只需要进行判断其是否存在,而没有特别复杂的操作,那么位图将是一个不错的选择。注意位图也是在内存中处理的,如果数据量过大,单纯使用位图无法处理的话,可以考虑使用哈希切割进行分块,然后再结合位图进行处理。

你可能感兴趣的:(java,算法,数据结构)