海量数据下使用bitmap和布隆过滤器去重
常规情况下的hash表去重
对于常规的少量数据去重,我们往往采用hash表来去重。这种方法比较简单,就是计算出hash值进行比较,然后hash值相同的在进行下额外的判断来确认是否是同一个数据即可。
bitmap去重
在海量数据进行去重的条件下,如果继续使用hash表则会出现一个很严重的问题,由于hash表是建立了一个hash值与实际数据的意义对应关系,对于十万乃至百万、千万的数据则完全是不现实的。首先对于去重的问题,我们只需要标记该数据有没有出现过,并不需要去存储该数据。从这方面入手,便有了bitmap。
这里我们假设待处理的数据是int型数据,占32位。则数值区间为2^32,一共需要2^32位来标识所有的数值,那么我们只需要含有2^29个byte的byte数组来表示即可。使byte[0]表示最低的8位,依次类推,按顺序排列。数据13则对应byte[1]中第5位设置为1,8+5=13,表示13出现过。
这里编程实现时一般采用位运算进行。例如
设置该数值出现过:public void setFlag(int num) { flags[num >> 3] = flags[num >> 3]|(0x01 << (num & (0x07))); }
首先num>>3表示将num左移三位,等价于num/8,这是应为每一个byte占8个字节相当于标记0-7。8/8=1则其标记位在byte[1]中。num & (0x07)则相当于num%8,高位与上0111后为都为0,只有最后三位保留下来。这样值域就是0-7。
查询制定数据是否出现过:public static boolean getFlag(int num) { return flags[num >> 3] >> (num & (0x07)) & 0x01; }
显然bitmap的大小不会随数据量的变化而变化,他只受原始数据分布区间的影响,所以非常适合来处理海量数据。但是如果数据量比较小的话,它的效率可能就稍逊于hash表了。
布隆过滤器去重,
bitmap虽然好用,但是对于很多实际情况下的大数据处理它还是远远不够的,例如如果我们要进行64bit的long型数据去重,那我们需要含有2^61个byte的byte数组来存储,这显然是不现实的。那我们如何来优化呢,很明显加入我们申请了这么打的byte数组来标记数据,可想而知其空间利用率是极地的。布隆过滤器正是通过提高空间利用率来进行标记的。
如下图所示
它采用k个不同的散列函数来进行计算,将数值散列到k个不同的位置,然后将这些位置置为1.上图为例,k=3对于数值w他所对应的三个数值不全为1,所以判定其不在集合中。想到这里大家应该意识到了一个问题,如果一个数值v进行散列计算后位置落在3,4,5(从0开始计算),那么将判定该数据在当前集合中,这就产生了误判。会将不在集合中的数据误认为存在集合中,但是判定不在集合中的情况那将一定不在集合中。我们将这种错误判断的概率叫做误判率。接下来我们计算一下误判率:
假设目前有m个bit,k个散列函数,集合中存在n个数据。
当k=1时,插入一个元素后某个位置为0的概率为
1−1m 1 − 1 m
插入n个后某个位置还为0的概率为
(1−1m)n ( 1 − 1 m ) n
每次插入元素时有k个散列函数,相当于把k个位置置为1。所以k个散列函数插入n个数据后某一个位置为0的概率为
(1−1m)nk ( 1 − 1 m ) n k
为1的概率则为
1−(1−1m)nk 1 − ( 1 − 1 m ) n k
所以当插入n个元素后,查找某个元素是否在集合中事。k个散列位置全为1的概率为(产生误判,比真实值要大,应为这个数可能真的在集合中)
(1−(1−1m)nk)k ( 1 − ( 1 − 1 m ) n k ) k
具体往下的推导大家可以自行百度,看看paper。这里只是简单介绍一下。