【轉】海量數據解決思路之BitMap
轉載(http://zengzhaozheng.blog.51cto.com/8219051/1404108)
一、概述
本文將講述Bit-Map算法的相關原理,Bit-Map算法的一些利用場景,例如BitMap解決海量數據尋找重復、判斷個別元素是否在海量數據當中等問題.最後說說BitMap的特點已經在各個場景的使用性。
二、Bit-Map算法
先看看這樣的一個場景:給一臺普通PC,2G內存,要求處理一個包含40億個不重復並且沒有排過序的無符號的int整數,給出一個整數,問如果快速地判斷這個整數是否在文件40億個數據當中?
問題思考:
40億個int占(40億*4)/1024/1024/1024 大概為14.9G左右,很明顯內存只有2G,放不下,因此不可能將這40億數據放到內存中計算。要快速的解決這個問題最好的方案就是將數據擱內存了,所以現在的問題就在如何在2G內存空間以內存儲著40億整數。一個int整數在java中是占4個字節的即要32bit位,如果能夠用一個bit位來標識一個int整數那麽存儲空間將大大減少,算一下40億個int需要的內存空間為40億/8/1024/1024大概為476.83 mb,這樣的話我們完全可以將這40億個int數放到內存中進行處理。
具體思路:
1個int占4字節即4*8=32位,那麽我們只需要申請一個int數組長度為 int tmp[1+N/32]即可存儲完這些數據,其中N代表要進行查找的總數,tmp中的每個元素在內存在占32位可以對應表示十進制數0~31,所以可得到BitMap表:
tmp[0]:可表示0~31
tmp[1]:可表示32~63
tmp[2]可表示64~95
.......
那麽接下來就看看十進制數如何轉換為對應的bit位:
假設這40億int數據為:6,3,8,32,36,......,那麽具體的BitMap表示為:
如何判斷int數字在tmp數組的哪個下標,這個其實可以通過直接除以32取整數部分,例如:整數8除以32取整等於0,那麽8就在tmp[0]上。另外,我們如何知道了8在tmp[0]中的32個位中的哪個位,這種情況直接mod上32就ok,又如整數8,在tmp[0]中的第8 mod上32等於8,那麽整數8就在tmp[0]中的第八個bit位(從右邊數起)。
三、Bit-Map算法原始實現
標註下,這部分來自blog:http://blog.csdn.net/hguisu/article/details/7880288的第五部分。好,來看看c語言的實現:
//set 設置所在的bit位為1 void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); } //clr 初始化所有的bit位為0 void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK)); } //test 測試所在的bit為是否為1int test(int i){ return a[i>>SHIFT] & (1<<(i & MASK)); } int main() { int i; for (i = 0; i < N; i++) clr(i); while (scanf("%d", &i) != EOF) set(i); for (i = 0; i < N; i++) if (test(i)) printf("%d\n", i); return 0; }
註明: 左移n位就是乘以2的n次方,右移n位就是除以2的n次方
解析本例中的void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }
1) i>>SHIFT:
其中SHIFT=5,即i右移5為,2^5=32,相當於i/32,即求出十進制i對應在數組a中的下標。比如i=20,通過i>>SHIFT=20>>5=0 可求得i=20的下標為0;
2) i & MASK:
其中MASK=0X1F,十六進制轉化為十進制為31,二進制為0001 1111,i&(0001 1111)相當於保留i的後5位。
比如i=23,二進制為:0001 0111,那麽
0001 0111
& 0001 1111 = 0001 0111 十進制為:23
比如i=83,二進制為:0000 0000 0101 0011,那麽
0000 0000 0101 0011
& 0000 0000 0001 0000 = 0000 0000 0001 0011 十進制為:19
i & MASK相當於i%32。
3) 1<<(i & MASK)
相當於把1左移 (i & MASK)位。
比如(i & MASK)=20,那麽i<<20就相當於:
0000 0000 0000 0000 0000 0000 0000 0001 << 20
=0000 0000 0001 0000 0000 0000 0000 0000
註意上面 “|=”.
在博文:位運算符及其應用 提到過這樣位運算應用:
將int型變量a的第k位清0,即a=a&~(1<<k)
將int型變量a的第k位置1, 即a=a|(1<<k)
這裏的將 a[i/32] |= (1<<M));第M位置1 .
4) void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }等價於:
-
void set(int i)
-
{
-
a[i/32] |= (1<<(i%32));
-
}
即實現上面提到的三步:
1.求十進制0-N對應在數組a中的下標: n/32
2.求0-N對應0-31中的數:N%32=M
3.利用移位0-31使得對應32bit位為1: 1<<M,並置1;
四、BitMap算法一些其他應用場景擴展
(1)BitMap小小變種:2-BitMap。
看個小場景:在3億個整數中找出不重復的整數,限制內存不足以容納3億個整數。
對於這種場景我可以采用2-BitMap來解決,即為每個整數分配2bit,用不同的0、1組合來標識特殊意思,如00表示此整數沒有出現過,01表示出現一次,11表示出現過多次,就可以找出重復的整數了,其需要的內存空間是正常BitMap的2倍,為:3億*2/8/1024/1024=71.5MB。
具體的過程如下:
掃描著3億個整數,組BitMap,先查看BitMap中的對應位置,如果00則變成01,是01則變成11,是11則保持不變,當將3億個整數掃描完之後也就是說整個BitMap已經組裝完畢。最後查看BitMap將對應位為11的整數輸出即可。
(2)對沒有重復元素的整數進行排序。
對於非重復的整數排序BitMap有著天然的優勢,它只需要將給出的無重復整數掃描完畢,組裝成為BitMap之後,那麽直接遍歷一遍Bit區域就可以達到排序效果了。
舉個例子:對整數4、3、1、7、6進行排序
BitMap如下:
直接按Bit位輸出就可以得到排序結果了。
五、總結
本文主要講述了BitMap算法的相關概念以及其一些相關的應用場景和實現方法。其實BitMap的應用場景遠遠不止點,比如還可以用於壓縮、爬蟲系統中url去重、解決全組合問題。可能有些人覺得BitMap算法實現起來有點麻煩,其實某些語言是對BitMap算法進行了封裝的,比如java中對應BitMap的數據結構就有BitSet類。其使用方法相當簡單,看看API就ok,還是給個例子吧:
import java.util.BitSet; public class Test{ public static void main(String[] args) { int [] array = new int [] {1,2,3,22,0,3}; BitSet bitSet = new BitSet(6); //將數組內容組bitmap for(int i=0;i<array.length;i++) { bitSet.set(array[i], true); } System.out.println(bitSet.size()); System.out.println(bitSet.get(3)); } }
對應的bit位如果有對應整數那麽通過bitSet.get(x)會返回true,反之false。其中x為BitMap位置下標。
好了,BitMap就說到這裏。下次blog說說處理海量數據的“萬金油”-Hash算法,以及它在MapReduce框架中的應用。
參考文獻:
http://blog.csdn.net/v_july_v/article/details/6685962
文章第三部分來自於:http://blog.csdn.net/hguisu/article/details/7880288
【轉】海量數據解決思路之BitMap