bitMap原理及實戰

Algoric發表於2018-12-25

小知識

  1. 在實際專案中,我們經常需要聚合統計,比如統計一個年齡在20-30,喜歡看技術書籍,喜歡聽音樂,喜歡宅在家的程式設計師等等一系列標籤的使用者。 如果使用mysql求並集,首先語句隨著標籤變長而變長,其次聚合,分組,去重嚴重影響語句效能。這種情況如何解決?
  2. 比如現在比較火的面試題,在10億整數中找出100個重複的數,或者任意給定一個整數,判斷是否在這個10億數中。

bitMap原理

bitMap就是使用bit位來標記元素,key為該元素,value一般為0或者1,大大節省儲存空間.

現在有(2, 3, 4, 5,7)5個數,任意給定一個在0-7範圍內的數字,判斷是否在此集合中:

bitMap原理及實戰
  1. 建立一個範圍為(0-7)的Byte型別陣列,將集合數字對應陣列的bit位置置1;
  2. 然後遍歷該Byte陣列,如果Byte陣列位置為1即代表該數存在。
  3. 同理對於10億整數也可以這樣處理,一個int型數字4個位元組,32bit,如果使用bit標記正整數,就可以節省32倍的記憶體空間。
  4. 10億數字,40億位元組,320億bit,需要大約4g記憶體,使用bitMap標記,只需要125M記憶體空間即可儲存,大大節省記憶體空間。

以上和桶排序排序的思想非常相似。

bitMap實際運用

對於1千萬資料,判斷任意給定的數是否在其中?
分治思想
使用int陣列作為bitMap。
將陣列分成32組,每組內有(0-31)個位置,如果給定陣列在指定陣列中的bit是0,則不存在。

  1. 求十進位制數在對應陣列a中的下標
    a[i] = a[N/32]

  2. 求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");
        }
    }
}
複製程式碼

注:本段程式碼有一些移位運算,感興趣可以研究下

相關文章