小知識
- 在實際專案中,我們經常需要聚合統計,比如統計一個年齡在20-30,喜歡看技術書籍,喜歡聽音樂,喜歡宅在家的程式設計師等等一系列標籤的使用者。 如果使用mysql求並集,首先語句隨著標籤變長而變長,其次聚合,分組,去重嚴重影響語句效能。這種情況如何解決?
- 比如現在比較火的面試題,在10億整數中找出100個重複的數,或者任意給定一個整數,判斷是否在這個10億數中。
bitMap原理
bitMap就是使用bit位來標記元素,key為該元素,value一般為0或者1,大大節省儲存空間.
現在有(2, 3, 4, 5,7)5個數,任意給定一個在0-7範圍內的數字,判斷是否在此集合中:
- 建立一個範圍為(0-7)的Byte型別陣列,將集合數字對應陣列的bit位置置1;
- 然後遍歷該Byte陣列,如果Byte陣列位置為1即代表該數存在。
- 同理對於10億整數也可以這樣處理,一個int型數字4個位元組,32bit,如果使用bit標記正整數,就可以節省32倍的記憶體空間。
- 10億數字,40億位元組,320億bit,需要大約4g記憶體,使用bitMap標記,只需要125M記憶體空間即可儲存,大大節省記憶體空間。
以上和桶排序排序的思想非常相似。
bitMap實際運用
對於1千萬資料,判斷任意給定的數是否在其中?
分治思想
使用int陣列作為bitMap。
將陣列分成32組,每組內有(0-31)個位置,如果給定陣列在指定陣列中的bit是0,則不存在。
-
求十進位制數在對應陣列a中的下標
a[i] = a[N/32] -
求int[]中bit位置
index = a[i] % 32
上述兩個運算可以改成位運算,因為位運算的效率非常高,佔用cpu的時鐘週期非常少。
結論:對於2的倍數,%2^n = &(2^n-1),模運算等於與預算,例:a % 16 = a & 15,這裡的15做與運算時需要化成16進位制,即0x0F.
在10000000個範圍為[1-100000]數中,給定指定一個數,判斷是否在這個集合中
程式碼:
public class BitMapActual {
//1千萬資料集合
private static final int N = 10000000;
//bitmap陣列
private static int[] a = new int[N/32 + 1]; //int 等於32個bit 所以資料長度為(N/32+1)
/**
* 為集合資料加標記
* @param n
*/
public static void addValue(int n) {
//定位陣列編號 相當於n/32
int row = n >> 5;
//定位陣列內slot位置 相當於n%32
int offset = n & 0x1F;
//陣列slot置1
a[row] |= 1 << offset;
}
/**
* 判斷給定數字是否存在
* @param n
* @return
*/
public static boolean exits(int n) {
//定位陣列編號
int row = n >> 5;
//定位陣列內slot位置
int offset = n & 0x1F;
// 1 << position 將a[i]中左移position求與,slot位置有值返回true
return (a[row] & ( 1 << offset)) != 0;
}
public static void main(String[] args) {
//初始化一個長度為N的陣列
int num[] = new int[N];
Random random = new Random();
for (int i = 0; i < N; i++) {
//隨機數範圍是(0-100000)
int item = random.nextInt(100000);
num[i] = item;
}
BitMapActual map = new BitMapActual();
//置1
for(int i = 0; i < N; i++){
map.addValue(num[i]);
}
int temp = 200;
if(map.exits(temp)){
System.out.println("temp:" + temp + "has already exists");
} else {
System.out.println("temp:" + temp + "has no exists");
}
}
}
複製程式碼
注:本段程式碼有一些移位運算,感興趣可以研究下