Bit-map法處理大資料問題

來源:互聯網
上載者:User

標籤:

問題引入:

1.給40億個不重複的unsigned int的整數,沒排過序的,然後再給一個數,如何快速判斷這個數是否在那40億個數當中?
2.給定一個千萬層級資料量的整數集合,判斷哪些是重複元素。
3.給定一個千萬層級資料量的整形數組,對其進行排序。
4.在5億個整數中找出不重複的整數(注意,記憶體不足以容納這5億個整數)。

從資料量上看,使用常規的解法(普通排序演算法,逐個比較等)明顯不合適,所以這裡我們引入一個新的解法,就是Bitmap。

Bitmap就是用一個bit位來標記某個元素對應的Value, 而Key即是該bit的位序。由於採用了Bit為單位來儲存資料,因此可以大大節省儲存空間。 bitmap通過1個位表示一個狀態,比如:int類型有2^32個數字,即4G個數字,那麼每個數字一個狀態,就是2^32個bit,即512 MB(也就是說,用512兆儲存空間就可以處理4G個資料,即40+億資料)。

下面是我用C++寫的一個bitmap類,可以通過構造對象時傳入資料規模,動態申請所需的記憶體,然後處理使用者的大量資料:

  1 #include<iostream>  2 #include<fstream>  3 #include<ctime>  4 using namespace std;  5 const unsigned SIZE = 512000000;//512兆靜態儲存區可處理40.96億資料  6   7 class Bitmap {  8     typedef struct Byte {  9         unsigned char bit8; 10         static const unsigned char mask[9];//用來取得一個位元組每一位的輔助數組 11         Byte() 12         { 13             bit8 = 0; 14         } 15         //設定該位,就是儲存該數 16         void set1(unsigned at) 17         { 18             bit8 |= mask[at]; 19         } 20         //讀取該位是否有數 21         bool get1(unsigned at) 22         { 23             return bit8 & mask[at]; 24         } 25     } Byte; 26     Byte *m_byte; 27     unsigned m_size; 28 public: 29     Bitmap(unsigned _size) 30     { 31         m_byte = new Byte[(_size+7)/8]; 32         m_size = _size; 33     } 34     virtual ~Bitmap() 35     { 36         delete[] m_byte; 37         m_size = 0; 38     } 39     //儲存一個資料 40     bool push(unsigned data) 41     { 42         if(data>=m_size) 43             return false; 44         m_byte[data/8].set1(data%8); 45         return true; 46     } 47     //讀取一個資料是否存在 48     bool find(unsigned data) 49     { 50         return data>=m_size ? 0 : m_byte[data/8].get1(data%8); 51     } 52     //返回能儲存的資料個數 53     unsigned size() 54     { 55         return m_size; 56     } 57     //重載運算子實現常用功能 58     //儲存一個資料 59     bool operator>>(unsigned data) 60     { 61         return push(data); 62     } 63     //讀取一個資料是否存在 64     bool operator<<(unsigned data) 65     { 66         return find(data); 67     } 68     //訪問到某個資料區塊 69     Byte& operator[](unsigned i) 70     { 71         if(i>=m_size/8) 72             throw "index out of range"; 73         return m_byte[i]; 74     } 75 }; 76 const unsigned char Bitmap::Byte::mask[9] = {0x80,0x40,0x20,0x10,0x8,0x4,0x2,0x1};//用來取得一個位元組每一位的輔助數組 77  78 int main() 79 { 80     Bitmap bitmap(8*SIZE);//可以儲存40+億資料 81     ifstream file("in.txt"); 82     unsigned read, i=0, t1 = clock(); 83     for(i=0; i<SIZE; ++i) 84         if(file>>read) 85             bitmap>>read; 86         else 87             break; 88     file.close(); 89     cout<<"共儲存"<<i/10000<<"W 資料, "<<"耗時: "<<clock()-t1<<"ms"<<endl; 90     t1 = clock(); 91     for(i=0; i<1000000; ++i) 92         if(bitmap<<i) 93             ; 94     cout<<"訪問"<<i/10000<<"W 資料共耗時: "<<clock()-t1<<"ms"<<endl; 95     cout<<"請輸入需要檢索的資料:"<<endl; 96     while(cin>>read) { 97         if(bitmap<<read) 98             cout<<"已儲存"<<read<<endl; 99         else100             cout<<"Error: 未儲存"<<read<<endl;101     }102     return 0;103 }

 運行結果如下:

在程式中,先讀取一個文字檔中隨機產生的6W個整數,存到這個bitmap中,然後測試了一下從這個建立好的bitmap中尋找100W資料耗時情況(11ms左右),接下來的部分是使用者可以手動輸入一些整數,程式會自動檢索bitmap中是否已儲存該資料。

這樣就可以解決引入話題中的第一個問題了,把輸入的文本資料改為已知的40億資料就可以了(40億資料的錄入可能需要一會兒,大概1300秒)。

下面給出引入的剩餘三個問題的解題思路。

問題2:先建立一個足夠大的Bitmap對象,然後依次錄入這些資料,如果錄入某資料前發現該位已經為1,則該資料重複,依次得到重複的資料即可。

問題3:先建立一個足夠大的Bitmap對象,然後依次錄入這些資料,從Bitmap開始位置起遍曆,如果某位不為0,則表示有該資料,依次輸出不為0的位的位序就是排序好的數組(輸出太多沒意義,可以將輸出轉換到寫入檔案,那麼新檔案中資料就是排序好的)。

問題4:方法1,建立2個足夠大的Bitmap對象,依次錄入資料,錄入前先判斷該資料在bitmap1中是否存在(即對應位是否為1),不存在則錄入到bitmap1中,存在就錄入到bitmap2中;全部錄入完後,依次遍曆bitmap1中每一位,如果某一位為1但是bitmap2中對應位不為1,則表示該資料只出現過一次,依次輸出即可。

          方法2,建立一個足夠大的Bitmap對象,不過用兩位表示一個資料,00表示資料不存在,01資料出現一次,10表示資料出現多次。11呢?一邊涼快去吧,不要你了,哈哈。這樣依次錄入資料時,如果該對應位(其實是兩位)為00則改為01,01就改為10,10就不管了。錄入完成後,遍曆整個bitmap,找到01位就輸出。

  好了,常見的大資料題目就通過bitmap這個神奇的結構給解決了,不過bitmap也不是萬能的,很明顯,它暫時只適合儲存整形資料,當然這裡只考慮了unsigned類型資料,如果是int類型的話,對應映射一下就可以了也是沒問題的。不過即使如此,也只能處理10億層級的資料,如果資料量更大、類型不只是整形呢?

  比如:需要寫一個網路蜘蛛(web crawler)。由於網路間的連結錯綜複雜,蜘蛛在網路間爬行很可能會形成“環”。為了避免形成“環”,就需要知道蜘蛛已經訪問過哪那些URL。給一個URL,怎樣知道蜘蛛是否已經訪問過?

  不難想到如下幾種方案:

  1. 將訪問過的URL全部儲存到資料庫;

  2. 用HashSet將訪問過的URL儲存起來。那隻需接近O(1)的代價就可以查到一個URL是否被訪問過;

  3. URL經過MD5或SHA-1等單向雜湊後再儲存到HashSet或資料庫。

  4. Bit-Map方法。建立一個BitSet,將每個URL經過一個雜湊函數映射到某一位。

  方法1~3都是將訪問過的URL完整儲存,方法4則只標記URL的一個映射位。

  以上方法在資料量較小的情況下都能完美解決問題,但是當資料量變得非常龐大時問題就來了。

  方法1:資料量變得非常龐大後關係型資料庫查詢的效率會變得很低。而且每來一個URL就啟動一次資料庫查詢是不是太小題大做了?

  方法2:太消耗記憶體。隨著URL的增多,佔用的記憶體會越來越多。就算只有1億個URL,每個URL只算50個字元,就需要5GB記憶體。

  方法3:由於字串經過MD5處理後的資訊摘要長度只有128Bit,SHA-1處理後也只有160Bit,因此方法3比方法2節省了好幾倍的記憶體。

  方法4:消耗記憶體是相對較少的,但缺點是單一雜湊函數發生衝突的機率太高。還記得資料結構課上學過的Hash表衝突的各種解決方案嗎?若要降低衝突發生的機率到1%,就要將BitSet的長度設定為URL個數的100倍。

  但是我們可以考慮如果在一定程度上忽略誤判的情況,那麼是不是可以通過改進方法4實現這一功能?其實這就是Bloom Filter的演算法 的思想:Bloom Filter是由Bloom在1970年提出的一種多雜湊函數映射的快速尋找演算法。通常應用在一些需要快速判斷某個元素是否屬於集合,但是並不嚴格要求100%正確的場合。其思想就是在方法4基礎上做了一些改進,不是映射到一位,而是通過K個雜湊函數映射到K位上,這樣只有當新的URL計算得到的K位都為1時才判斷為該URL已經訪問過(有誤判的可能性,不過有相關研究證明,取得合適的K值和bitmap位元時可以讓誤判率很小以至於可以忽略,參見細節)

  當然,還可以通過map-reduce來處理,畢竟人家mapreduce可是行家,專業的大資料處理技術嘛!

  參考文獻:

  Bitmap位元影像法

  BloomFilter——大規模資料處理利器

  BloomFilter概念及原理 

  歡迎瀏覽我的部落格,小生初來乍到,哪裡有問題請多多指教,轉載請註明出處!http://www.cnblogs.com/webary/p/4733247.html

Bit-map法處理大資料問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.