大数据常用技巧之位图法

大数据常用技巧之位图法

介绍

位图的基本概念是用一个位(bit)来标记某个数据的存放状态,由于采用了位为单位来存放数据,所以节省了大量的空间。举个具体的例子,在Java中一般一个int数字要占用32位,如果能用一位就表示这个数,就可以缩减大量的存储空间。一般把这种方法称为位图法,即Bitmap。

BitSet

正因为位图运算在空间方面的优越性,很多语言都有直接对它的支持。如在C++的STL库中就有一个bitset容器。而在Java中,在java.util包下也有一个BitSet类用来实现位图运算。此类实现了一个按需增长的位向量。BitSet的每一位都由一个boolean值来表示。用非负的整数将BitSet的位编入索引,可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个BitSet修改另一个BitSet的内容。

需要注意的是BitSet底层实现是通过一个long数组来保存数据的,也就是说它增长的最小单位是一个long所占的逻辑位,即64位。但如果不是对存储区空间有极致的要求,而且对自己的基本功非常有信心,不建议自己去实现一个跟BitSet类似的类来实现相关的功能。因为jdk中的类都是极精简并做过合理优化的,BitSet类比较长,举其中一个方法来说。

    //这是用来在两个BitSet之间做或运算的方法
    public void or(BitSet set) {
        //自己与自己做或运算结果不变
        if (this == set)
            return;
        int wordsInCommon = Math.min(wordsInUse, set.wordsInUse);
        //考虑是否需要扩容,因为参数可能比原来的set要长
        if (wordsInUse < set.wordsInUse) {
            ensureCapacity(set.wordsInUse);
            wordsInUse = set.wordsInUse;
        }
        //长的BitSet超出短的的部分结果不变
        for (int i = 0; i < wordsInCommon; i++)
            words[i] |= set.words[i];
        if (wordsInCommon < set.wordsInUse)
            System.arraycopy(set.words, wordsInCommon,
                             words, wordsInCommon,
                             wordsInUse - wordsInCommon);
        //参数校验,确保所有参数之间关系正确
        checkInvariants();
    }

典型应用

海量数据排序

从最简单的情况说起,如果要对90个小于100的不重复的正整数排序。用位图的思想就是先申请一块100bit的空间,第一遍遍历所有的数,将出现的数字在位图中对应的位置置为1;第二遍遍历位图,依次输出值为1的位对应的数字。先且不说这种情况出现的频率不是很高,就仅这种情况,还是有很多其他的排序算法有它们自己的优势(不用额外占用空间之类)。但更进一步,如果我们把数字范围放大,对1000,000,000中的900,000,000个不重复的正整数排序,由于需要的内存非常大,其他算法在不分治的情况下就很难再处理这个问题。而用位图法只需要1000000000/(8*1024*104)=119.2MB空间,对现在的计算机来说没有任何问题。

海量数据去重

看一个比较常见的面试题:在2.5亿个整数中找出不重复的整数,内存不足以放下算有的数。我们可以采用两位的位图法,为每个数分配两位,00表示没有出现,01表示出现一次,10表示出现多次,11没有意义。这样下来总共需要 2322=1 GB(这里没有限定整数的范围,所有把所有32位整数都考虑进去)的内存。接下去扫描着2.5亿个整数,查看位图中相对应的位,如果是00就变为01,如果是01就变为10,其他情况保持不变。扫描完成后仍为01的整数就是需要查找的数。

数据压缩

假设有这样一份数据,记录了全国1990-1999年出生的人的姓名和出生年月的键值对。假设正好有一千万人,那就要存储一千万个姓名和年份。如何运用Bitmap的思想来压缩数据呢。下面提供几种思路。

从人的角度来看,由于一共就只有10个年份,可以用4个bit将它们区分开。如0000表示1990年,1001表示1999年。那一个人的出生年份就可以用4个bit位来表示,进而一千万个年份就可以压缩为一千万个4位的bit组;从另一个角度来看这个问题,我们有10个年份,每个人要么是要么不是在这个年份出生。每个人对于年份来说就可以抽象为一个bit位,所以我们可以把一千万的年龄压缩为10个一千万位的bit组。这样压缩的力度不如按人的角度压缩的大,但从年份出发的问题会有一定的优势,如有哪些人是1990年出生的,只需遍历1990年对应的那个bit组就可以了。

可以看出来不管从哪个角度,bitmap的压缩都是建立在数据中存在大量的冗余数据的基础上的,如年份。而在上面的问题中,年份的分布是散乱的,那假如我们事先把数据进行了排序,把相同的出生年份的人排在一起,那数据就可以进一步压缩。这样一来就只要记录每个年份的人数,就可以根据下标来判断每个人的出生年份。

总结

Bitmap适用于数据规模大,但数据状态少的情况。同时Bitmap在存在以下一些不足:

  • 存储离散数据利用率低
    Bitmap申请空间时要根据最大的数来决定申请的空间大小,如果数据是离散的,那空间的利用率就会非常低。
  • 不适合多状态
    一个bit只能表示两种状态,如果要表示更多的状态,就需要更多的状态位来实现。如果一个数字需要多个状态位来表示的话,Bitmap的优越性也会大打折扣,而且复杂度却在增加。
  • 可读性差
    将数据抽象为bit不利于理解,尤其是用多个bit位来表示一个数时。
  • 性能一般
    需要维护额外的逻辑,计算速度会受到一定的影响。

你可能感兴趣的:(大数据,位图)