1. 程式人生 > >BitMap點陣圖與海量資料的理解

BitMap點陣圖與海量資料的理解

1. Bit Map演算法簡介

        來自於《程式設計珠璣》。所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。由於採用了Bit為單位來儲存資料,因此在儲存空間方面,可以大大節省。

 

2、 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圖。

 

3、 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位。 

如題:

給你一個檔案,裡面包含40億個整數,寫一個演算法找出該檔案中不包含的一個整數, 假設你有1GB記憶體可用。

如果你只有10MB的記憶體呢?

一個位代表一個數據,那40一個數據大概要40*10^8*bit = 0.5GB,滿足記憶體要求。

首先我們用int來表示:int  bmap[1+N/32]; //N是總數,N=40億,一個int32bit

然後我們插入一個整數val,要先計算val位於陣列bmap中的索引:index = val/32;

比如整數33,index=33/32=1,第33位於陣列中的index=1

比如整數67,index=67/32=2,位於陣列中index=2

然後在計算在這個index中的位置,因為陣列中的每個元素有32位

33,index=1,在1中的位置為33%32=1

67,index=2,在2中的位置為67%32=3

然後就是標識這個位置為1:

bmap[val/32]  |= (1<<(val%32));

33: bmap[1]    != (1<<1);//xxxxxx1x,紅絲位置被置為1

67: bmap[2]   !=  (1<<3);//xxxx1xxx

void setVal(int val)
{
    bmap[val/32] |= (1<<(val%32));
    //bmap[val>>5] != (val&0x1F);//這個更快?
}

 

怎樣檢測整數是否存在?

比如我們檢測33,同樣我們需要計算index,以及在index元素中的位置

33: index = 1, 在bmap[1]中的位置為 1,只需要檢測這個位置是否為1

bmp[1] &(1<<1),這樣是1返回true,否側返回false

67:bmp[2]&(1<<3)

127:bmp[3]&(1<<31)

bool testVal(int val)
{
    return bmap[val/32] & (1<<(val%32));
    //return bmap[val>>5] & (val&0x1F);
}

 

現在我們來看如果記憶體要求是10MB呢?

這當然不能用bitmap來直接計算。因為從40億資料找出一個不存在的資料,我們可以將這麼多的資料分成許

多塊, 比如每一個塊的大小是1000,那麼第一塊儲存的就是0到999的數,第2塊儲存的就是1000 到1999的數……

實際上我們並不儲存這些數,而是給每一個塊設定一個計數器。 這樣每讀入一個數,我們就在它所在的塊對應的計數器加1。

處理結束之後, 我們找到一個塊,它的計數器值小於塊大小(1000), 說明了這一段裡面一定有數字是檔案中所不包含的。然後我們單獨處理
這個塊即可。接下來我們就可以用Bit Map演算法了。我們再遍歷一遍資料, 把落在這個塊的數對應的位置1(我們要先把這個數
歸約到0到blocksize之間)。 最後我們找到這個塊中第一個為0的位,其對應的數就是一個沒有出現在該檔案中的數。)

 

4、 Bit-Map的應用

      1)可進行資料的快速查詢,判重,刪除,一般來說資料範圍是int的10倍以下。

       2)去重資料而達到壓縮資料

 

5、 具體實現(JAVA)

【問題例項】

1)已知某個檔案內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。

8位最多99 999 999,大概需要99m個bit,大概10幾m位元組的記憶體即可。

點陣圖法需要的空間很少(依賴於資料分佈,但是我們也可以通過一些放啊發對資料進行處理,使得資料變得密集),在資料比較密集的時候效率非常高。例如:8位整數可以表示的最大十進位制數值為99999999,如果每個陣列對應於一個bit位,那麼把所有的八進位制整數儲存起來只需要:99Mbit = 12.375MB.

實際上,Java jdk1.0已經提供了bitmap的實現BitSet類,不過其中的某些方法是jdk1.4之後才有的。

分別使用自己實現的BitMap和jdk的BitSet類:

//去除重複並排序
import java.util.Arrays;
import java.util.BitSet;
import java.util.Random;

/**
 * @author 8  * @date Time: 
 * @des:
 */
public class BitMap {
    int ARRNUM = 800;
    int LEN_INT = 32;
    int mmax = 9999;
    int mmin = 1000;
    int N = mmax - mmin + 1;

    public static void main(String args[]) {
         new BitMap().findDuplicate();
         new BitMap().findDup_jdk();
    }

    public void findDup_jdk() {
        System.out.println("*******呼叫JDK中的庫方法--開始********");
        BitSet bitArray = new BitSet(N);
        int[] array = getArray(ARRNUM);
        for (int i = 0; i < ARRNUM; i++) {
            bitArray.set(array[i] - mmin);
        }
        int count = 0;
        for (int j = 0; j < bitArray.length(); j++) {
            if (bitArray.get(j)) {
                System.out.print(j + mmin + " ");
                count++;
            }
        }
        System.out.println();
        System.out.println("排序後的陣列大小為:" + count );
        System.out.println("*******呼叫JDK中的庫方法--結束********");
    }
    //下面是自己實現的方法:
    public void findDuplicate() {
        int[] array = getArray(ARRNUM);
        int[] bitArray = setBit(array);
        printBitArray(bitArray);
    }

    public void printBitArray(int[] bitArray) {
        int count = 0;
        for (int i = 0; i < N; i++) {
            if (getBit(bitArray, i) != 0) {
                count++;
                System.out.print(i + mmin + "\t");
            }
        }
        System.out.println();
        System.out.println("去重排序後的陣列大小為:" + count);
    }

    public int getBit(int[] bitArray, int k) {// 1右移 k % 32位 與上 陣列下標為 k/32 位置的值
        return bitArray[k / LEN_INT] & (1 << (k % LEN_INT));
    }

    public int[] setBit(int[] array) {// 首先取得陣列位置下標 i/32, 然後 或上
                                        // 在該位置int型別數值的bit位:i % 32
        int m = array.length;
        int bit_arr_len = N / LEN_INT + 1;
        int[] bitArray = new int[bit_arr_len];
        for (int i = 0; i < m; i++) {
            int num = array[i] - mmin;
            bitArray[num / LEN_INT] |= (1 << (num % LEN_INT));
        }
        return bitArray;
    }

    public int[] getArray(int ARRNUM) {

        @SuppressWarnings("unused")
        int array1[] = { 1000, 1002, 1032, 1033, 6543, 9999, 1033, 1000 };

        int array[] = new int[ARRNUM];
        System.out.println("陣列大小:" + ARRNUM);
        Random r = new Random();
        for (int i = 0; i < ARRNUM; i++) {
            array[i] = r.nextInt(N) + mmin;
        }

        System.out.println(Arrays.toString(array));
        return array;
    }
}

2)2.5億個整數中找出不重複的整數的個數,記憶體空間不足以容納這2.5億個整數。 
將bit-map擴充套件一下,用2bit表示一個數即可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。

給你一個檔案,裡面包含40億個整數,寫一個演算法找出該檔案中不包含的一個整數, 假設你有1GB記憶體可用。

如果你只有10MB的記憶體呢?

這個是剛剛說到的那個題,具體java實現,跟1類似,有時間的話我來補充完整。