有這樣一種場景:一台普通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數放到內存中進行處理。
具體思路(BitMap思想):
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表示為:
(1)如何判斷int數字放在哪一個tmp數組中:將數字直接除以32取整數部分(x/32),例如:整數8除以32取整等於0,那么8就在tmp[0]上;
(2)如何確定數字放在32個位中的哪個位:將數字mod32(x%32)。上例中我們如何確定8在tmp[0]中的32個位中的哪個位,這種情況直接mod上32就ok,又如整數8,在tmp[0]中的第8 mod上32等於8,那么整數8就在tmp[0]中的第八個bit位(從右邊數起)。
一、什么是BitMap
Bit-Map會用Bit來標記某個元素對應的value,如何標記的呢,見下例: 我們現在有(1,2,5,8,10)數組,常規來說是這樣聲明的:
int[] array = {1, 2, 5, 8, 10}
上面這樣聲明會占用4×5個字節,即20個字節,少量數據可能沒有什么特別大的感覺,如果數組長度為10,000,000,這樣的方式就會占用4G的內存。
如果用Bit-Map的話,可以這樣來組織:
byte[] bytes = new bytes[2];
bytes[0] = 01100100; // 就直接寫二進制了
bytes[1] = 10100000;
例如:用位向量來表示數據: 1 、 3 、 6 、 10 、 100
// 1 3 6 10 100
BitSet bitSet = new BitSet(100);
bitSet.set(1,true);
bitSet.set(3,true);
bitSet.set(6,true);
bitSet.set(100,true);
for(int i=0;i<bitSet.size();i++){
boolean b = bitSet.get(i);
if(b){
System.out.println(i);
}
}
}
二、Bit-Map建立
1、開辟定長數組
Bit-Map會聲明一個定長的byte/int數組,之后將數組內元素的所有Bit位均置為0,如下圖:
2、遍歷數據,並插入Bit-Map
上例來說,就會遍歷array{1, 2, 5, 8, 10},並將所有的元素均插入Bit-Map中。Bit-Map是Hash的極致,那么key即為array[i]/8,value即在byte中的位置array[i]%8。而實際中為了效率,hash函數可能會有些出入。如下:
遍歷插入之后的數據應該是這樣的:
三、Bit-Map的基本思想
我們先來看一個具體的例子,假設我們要對0-7內的5個元素(4,7,2,5,3)排序(這里假設這些元素沒有重復)。那么我們就可以采用Bit-map的方法來達到排序的目的。要表示8個數,我們就只需要8個Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0,如下圖:
然后遍歷這5個元素,首先第一個元素是4,那么就把4對應的位置為1(可以這樣操作 p+(i/8)|(0x01<<(i%8)) 當然了這里的操作涉及到Big-ending和Little-ending的情況,這里默認為Big-ending),因為是從零開始的,所以要把第五位置為一(如下圖):
然后再處理第二個元素7,將第八位置為1,,接着再處理第三個元素,一直到最后處理完所有的元素,將相應的位置為1,這時候的內存的Bit位的狀態如下:
然后我們現在遍歷一遍Bit區域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的。
優點:1.運算效率高,不許進行比較和移位;
2.占用內存少,比如N=10000000;只需占用內存為N/8=1250000Byte=1.25M
缺點:
所有的數據不能重復。即不可對重復的數據進行排序和查找。
算法思想比較簡單,但關鍵是如何確定十進制的數映射到二進制bit位的map圖。
四、Map映射表
假設需要排序或者查找的總數N=10000000,那么我們需要申請內存空間的大小為int a[1 + N/32],其中:a[0]在內存中占32為可以對應十進制數0-31,依次類推:
bitmap表為:
a[0]--------->0-31
a[1]--------->32-63
a[2]--------->64-95
a[3]--------->96-127
..........
那么十進制數如何轉換為對應的bit位,下面介紹用位移將十進制數轉換為對應的bit位。
位移轉換
申請一個int一維數組,那么可以當作為列為32位的二維數組,
| 32位 |
int a[0] |0000000000000000000000000000000000000|
int a[1] |0000000000000000000000000000000000000|
………………
int a[N] |0000000000000000000000000000000000000|
例如十進制0,對應在a[0]所占的bit為中的第一位: 00000000000000000000000000000001
五、BitMap應用場景擴展
建立了Bit-Map之后,就可以方便的使用了。一般來說Bit-Map可作為數據的查找、去重、排序等操作。
如上面提及的10,000,000個數據存儲問題,用Integer存儲,耗費4G內存。改成Bit-Map,耗費125MB內存。但是實際中,可能由於數據中最大最小值相差太大,如{1,2 99999},只有三個數,但是最大最小相差懸殊,該方法就不適用了。
查找和去重都好理解,至於排序,有點類似桶排序,每個byte都是一個桶。
1、在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進行排序:
直接按Bit位輸出就可以得到排序結果了
3、已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數
8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了所有的8位數的電話。
4、2.5億個整數中找出不重復的整數的個數,內存空間不足以容納這2.5億個整數
將bit-map擴展一下,用2bit表示一個數即可:0表示未出現;1表示出現一次;2表示出現2次及以上,即重復,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。
關於BitMap的運用請參見:http://my.oschina.net/cloudcoder/blog/294810?fromerr=62qBkJF5
http://blog.csdn.net/hguisu/article/details/7880288
注:bitSet.size()返回此BitSet表示位值時實際使用空間的位數;一般為64的整數倍;
new BitSet(950)並不等於建立了一個950大小的BitSet,只是說構建出來的BitSet初始大小至少能容納950個Bit,大小永遠是系統控制的,而且它的大小是64的倍數,就算BitSet(1),它的大小也是64
BitSet能夠保證"如果判定結果為false,那么數據一定是不存在的,但是如果結果為true,那么數據可能存在,也可能不存在(沖突覆蓋)",即false==yes;true==maybe