BitMap算法

来源:互联网 发布:eml文件 mac 编辑:程序博客网 时间:2024/06/13 23:00

Bit-Map算法

简介:

1:Bit-Map算法又名位图算法,其原理是,使用下标代替数值或特定的意义,使用这个位为0或者1代表特性是否存在。
2:Bit-Map算法具有效率高,节省空间的特点,适用于对大量数据进行去重,查询等。

应用举例:

    例如,我们存储了一些整形数据:2,8,4,6,9,我们需要查询是否存储了3,那么,按普通的思路,我们需要将所有数据遍历一次,看是否有需要查找的数据,这样,时间复杂度为O(n),使用Bit-Map,我们可以申请10 bit 的空间,并将内容全部置零,需要存储2,那么就将下标2的bit位置为1,存储8,就将下标为8的bit位置为1,同样的,将下标为4,6,9的bit位也置为1,其结果如下图所示:

    图中,蓝色代表0,红色代表1。
    此时,如果我们需要查找3是否保存过,只需要直接访问下标3的bit位,为1,就说明保存过,为0就是未保存过。
    当然,这只是Bit-Map算法的最简单举例,主要是为了理解Bit-Map算法的实现方式。

Bit-Map算法实际运用:

    在实际中,例如某位QQ好友的信息页面中,我们常看见各种标签:宅男、90后、吃货、手机控……。再例如一些管理软件,通常会保存一些进度信息,比如物流订单状态:下订单、付款、发货……。

使用数据库:

    为了存储这些数据,我们很容易就想到了数据库,将腾讯所有存在的QQ号都保存在数据库中,添加很多的标签,记录每个QQ号有的标签,当我们查询的时候,就找到这个QQ号,就能查看到里面的所有标签,但是,当我们要访问哪些QQ是90后,哪些QQ是宅男,也是吃货,是手机控,是……,的时候,我们需要写很多SQL求交集等命令,导致命令非常长,难写而且效率低。当我们的标签上千上万后,就会非常地复杂。

使用Bit-Map算法:

    我们可以发现,这些信息都是是或者不是(是90后,不是90后)那么,这些数据都可以使用Bit-Map高效的存储起来,也可以非常方便地查询某一条记录的状态。

    首先,我们将所有QQ号按一定规则排序,每个QQ号都有自己对应的下标,如下图:


    我们让每一个标签都有独立的Bit-Map图,标签一般情况下是按照顺序排列的,于是,对于90后这个标签,我们可以建立一个BitMap,如下:

    其中,红色代表1,即是90后,蓝色代表0,即非90后,ID号及每个对象的ID,上图就可以代表ID为0~9的QQ号的是否为90后的状态,仅仅占10bit空间。同样,对于是否为吃货,是否使用的小米手机,是否是程序员等等,都是可以射阳表示的,每个标签对应一个BitMap图,ID个数(实际上是ID号的最大数值)决定了每个BitMap的大小。如果ID是不连续的,例如ID只有0和10000两个对象,那么按上面这种,每个BitMap就会占用10001个bit,对于这种特殊情况,后面在做讨论研究。

BitMap算法的数据获取:

    1:在对每个标签建BitMap图之后,他要获取即是90后,又是程序员的对象,就可以直接使用这两个标签的BitMap图相与,结果为一的ID,对应的对象就是要找的,当有多个条件的时候,同样只需要将多个标签相与即可得到结果,这里就不举例说明了,无论是空间还是时间上分析,效率都非常高。
    2:BitMap算法同样支持或运算,例如,我们要查找是90后或者00后的对象,同样也只需要将这两个标签的BitMap图相或即可得到结果。
    3:BitMap算法不支持非运算,在不使用其他方法处理的时候,我们不可以得到非90后的对象,如果直接对整个BitMap图做非运算,那么得到的是90后对象以外的所有,但这个所有并不是每一个都是对象,例如:总共有三个对象,ID为0和为1的对象为90后,ID为2的为00后,然而我们使用了10bit的空间存储,显然,3~9bit是没有对应对象的,直接做非运算,得到的结果显然是ID为3~9的也是非90后,显然是错的。
    非运算解决方案:我们可以做一个全量用户的BitMap,这个BitMap里面为1代表这个ID对应有对象,否则就是没有对象,这样,我们要得到非90后可以先做非运算,得到90后ID之外其他ID,再与全量用户做与运算,即可得到这些“非90后”里面,有对应对象的部分,这些就是实实际际的非90后对象。

BitMap算法缺点:

    1:BitMap不直接支持非运算,通过上述的方法可以简单解决,但需要对建立一个全量的BitMap图,这不算很致命的缺点,可以较容易的解决。
    2:添加对象不方便:每次我们需要添加对象的时候,就需要改变每一个BitMap图的长度,需要做大量的数据复制,空间分配运算,当然,我们可以像STL算法里面那样,每次分配两倍的空间(或者根据情况适当多分配一些空间),这样,添加少量的对象就不用做大量内存操作和文件操作,只需要将全量BitMap里面添加的ID设置成1,其他的标签也一样,该怎么变就怎么变。
    3:在之间插入ID,或者删除中间ID(例如,某个QQ号被回收,不再使用,很久以后又被使用),会导致空间浪费,如果对对象对应的ID顺序没有要求,可以直接查找前面空闲的空间,如果有要求,那么久只能做大量的数据操作。通常情况下,是没有要求的,而且一般一开始对象ID都会是按照顺序的,这样就不需要考虑这个问题。

    4:当我们分配的ID不连续的时候,会导致空间浪费,就像之前的,只有0和10000两个对象,每个标签就需要10001个bit来保存,非常浪费空间,这也是BitMap的最大缺点之一,谷歌在自己实现的EWAHCompressedBitMap中,就对BitMap存储空间做了一定的优化:

BitMap算法空间优化:

    EWAH把BitMap存储在long(64位)数组中,long数组的每一个元素都可以当作是一个64位的二进制,他是EWAH BitMap里面最小的单位,我们称一个long为一个Word,如下:

    现在我们插入一个ID是1的用户:Word1的内容就变成00000010B,再插入ID为4的用户,Word1的内容就变成了00010010B,ID为多少,对应的位就变为1,由此可见,一个Word最多可以保存64个ID,如果要保存ID为64的用户,那么Word2就变成00000001B,要保存ID为129的用户,Word3就变成了00000010B。但是,为什么是从Word1开始而非Word0呢?因为Word0适用于记录位置信息的。
    在EWAH BitMap算法中,Word分为两种,一种直接存储数据,称为Literal Word(LW),另一种保存跨度信息,称为Running Length Word(RLW)。我们已知LW的用处是直接存储的数据,下面对RLW存储的跨度信息做说明:
    RLW分为高32位和低32位,其中高32位用于保存当前RLW后面连续接了多少个LW,即表示后面跟了多少个Word的数据,低32位表示当前Word横跨了多少个空Word。例如:

    其中,Word0代表了后面有3个连续的LW(数据Word),自身横跨0个Word。Word4代表自身横跨6247个Word,后面连续存储了1个Word,我们知道每个Word最多存64个ID,因此,我们可知Word0后面的LW最大ID是:
64*3-1 = 191
    Word4说明了后面紧跟的为一个Word,因此,Word5能存储的最小ID为:
191+6247*64 = 399999
    Word5能存储的范围为399999~400063。上图中Word5存储的ID就是399999。
    对于其他标签的数据,同样是这样存储,不需要建立很长的BitMap来存储。

在之间插入:

    如果在上述优化的算法中,在之间插入ID为200000的ID,那么就会导致后面RLW的数据变化。但相比最原始的BitMap插入还是会快。但,任然不建议在之间插入,最好是按顺序插入。

声明:

    本文创作参考微信公众号“程序员小灰”——BitMap算法 整合版。



原创粉丝点击