淺談bitmap

1. 定義:
    從字面意思上理解,bitmap翻譯爲位圖,更準確地說應該是位的映射。bitmap通常應用於海量數據的處理,如查找、去重、排序。
    舉個例子:40億個int數字中,要找出只出現一次的數字集合。用普通的遍歷查找的話,40億int須要的內存空間是:40*10^8*4 = 16*10^9,即16GB的空間 (ps:1GB大約是10億字節)。對於通常計算機而言,內存大約2-8G,很明顯沒法存儲16GB的數據。若是用存磁盤的方式分次加載,須要大量的I/O消耗,性能不好。這時候,就要使用bitmap了,其核心思想是: 一個byte佔8個bit,若是用一個bit表示一個int數字的值,即0表示這個數不存在,1表示整個數存在,那麼一個byte就能表示8個int數字,一個int空間就能表示32個int數字。以下圖所示:
                         
 這樣的話,本來一個int數佔32bit,如今只佔1bit,即節省了32倍的空間。因此如今只須要16GB/32=512MB的內存空間,即只須要申請int bits[N/32 + 1]的空間就可存儲數據,其中 N表示這些數據中最大的數值數,此爲2^32。此外,因爲這些數字之間沒有關聯性,不須要同步處理,因此使用多線程的方式讀取和加載數據能夠實現更高的性能,時間複雜度大約是O(N/n),n表示線程數。
    注意:要說明的是,當有N個int數字用bitmap的方式存儲時,若是N個int數字的數值都在0-N的範圍內,那麼使用bitmap能夠節省32倍的內存空間;若是N個int數字的數值是0-MAXINT(即2^32)的範圍,那麼使用bitmap須要512MB的內存來存放全部的數,這樣的話若是N小於1.25億使用bitmap反倒多消耗了內存,只有N大於1.25億纔會節省內存,節省的內存倍數是:(4*N)B/512MB,如N爲10億int時,節省內存(4*10*10^8)B/512MB = 8。 
 
2. 具體方法:
      接下來談一下bitmap的實現方式。申請int bits[N/32+1]的空間後,一個int數如何定位到其索引位置及如何存放到bits數組中?如給定一個數33,咱們知道應該將其放入bits[1]的第二個bit位置。
    (1)肯定數組索引:使用數字除以32,即:num/32,也可寫:num>>5
    (2)肯定32位bit中的位置:使用數字對32取模,即:num%32,也可寫爲:num & 0x1F
    (3)數字存入bits中:bits[num/32] |= (1<<(num%32)),即:bits[num>>5] |= (1<<(num&0x1F))
    (4)數字從bits清除:bits[num/32] &= ~(1<<(num%32)),即:bits[num>>5] &= ~(1<<(num&0x1F))
    (5)判斷數字是否在bits中:return ( bits[num/32] &= (1<<(num%32)) ) != 0 )
 
3. 代碼實現:
        此處C++代碼默認N個int數字的數值範圍在0-N中,即bitmap能夠節省32倍的內存空間。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
class Bitmap
{
public:
    Bitmap(int N) :capacity(N) //使用bitmap節省空間
    {
        bits = new int[(capacity>>5)+1];
        for(int i=0; i<=(capacity>>5); ++i)
            bits[i] = 0;
    }
    ~Bitmap()
    {
        delete [] bits;
    }
    void Add(int num)    //添加數字num到bits中
    {
        bits[num>>5] |= (1<<(num&0x1F));
    }
    void Clear(int num)     //清除bits中的數字num
    {
        bits[num>>5] &= ~(1<<(num&0x1F));
    }
    string IsContain(int num)    //判斷num是否在bits中
    {
        return (( bits[num>>5] & (1<<(num&0x1F)) ) != 0) ? "YES" : "NO";
    }
    void Sort()   //對bits中的數排序,時間複雜度O(capacity)
    {
        int cnt = capacity>>5;   //肯定bits中的最大索引數 while(cnt >= 0)
        {
            for(int i=31; i>=0; --i)
            {
                if( (bits[cnt] & (1<<i)) != 0)
                    sortRes.push_back(cnt*32+i);
            }
            --cnt;
        }
    }
    void PrintSortRes()  //打印排序後的結果
    {
        cout<<"Sort :";
        for(auto i:sortRes)
            cout<<i<<" ";
        cout<<endl;
    }
private:
    int capacity; //存儲的int數據個數
    int *bits;    //指向存放數據的數組
    vector<int> sortRes;  //存放排序結果
};
int main()
{
    Bitmap bm(100);
    bm.Add(4);
    bm.Add(37);
    bm.Add(99);
    cout<<"4 in bits? "<<bm.IsContain(4)<<endl;
    cout<<"37 in bits? "<<bm.IsContain(37)<<endl;
    cout<<"99 in bits? "<<bm.IsContain(99)<<endl;
    cout<<"89 in bits? "<<bm.IsContain(89)<<endl;
    bm.Clear(4);
    cout<<"4 in bits? "<<bm.IsContain(4)<<endl;
    vector<int> SortRes;
    bm.Sort();
    bm.PrintSortRes();
    return 0;
}
 
4. 擴展:
         若是要找出上億整數中重複的數(屢次添加的數)個數,能夠用2-bitmap,即用2bit表示一個整數,00表示未出現,01表示出現一次,10表示出現屢次。在遍歷這些數時,若是對應位置是0則置爲1,若是是1則置爲2,若是是2則保持不變。遍歷的同時統計對應位置爲2的個數即爲答案。
相關文章
相關標籤/搜索