布隆過濾器
它實際上是一個很長的二進制向量和一系列隨機映射函數布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
優點:
相比於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優勢。布隆過濾器存儲空間和插入/查詢時間都是常數。另外, Hash函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優勢。
缺點:
誤算率是其中之一。隨着存入的元素數量增加,誤算率隨之增加。但是如果元素數量太少,則使用散列表足矣。
另外,一般情況下不能從布隆過濾器中刪除元素。我們很容易想到把位列陣變成整數數組,每插入一個元素相應的計數器加1, 這樣刪除元素時將計數器減掉就可以了。然而要保證安全的刪除元素並非如此簡單。首先我們必須保證刪除的元素的確在布隆過濾器裏面. 這一點單憑這個過濾器是無法保證的。另外計數器迴繞也會造成問題。
在降低誤算率方面,有不少工作,使得出現了很多布隆過濾器的變種。
BloomFilter.h
#pragma once #include<iostream> using namespace std; #include<vector> #include<string.h> class BitMap { public: BitMap(size_t size = 0)//size表示位的個數 :_size(0) { _a.resize((size >> 5) + 1);//+1是爲防止不能整除有餘數 } void Set(size_t x)//x對應位置1 { size_t index = x >> 5; //取到x在第index個size_t中 size_t num = x % 32; //取到x在第index個size_t中的第num位 if (!(_a[index] & (1 << num))) { ++_size; } _a[index] |= (1 << num); //對應位置1 //cout << "_a[index] "<<_a[index]<< endl; } void Reset(size_t size) //清空size對應的位 { size_t index = size >> 5; size_t num = size % 32; _a[index] &= ~(1 << num); _size--; //cout << "_a[index] " << _a[index] << endl; } bool Test(size_t size)//檢測size是否存在,也就是檢測size對應位是否爲1 { size_t index = size >> 5; size_t num = size % 32; if (_a[index] & (1 << num)) { return true; } return false; } void ReSize(size_t size) { _a.resize((size >> 5) + 1); } protected: vector<size_t> _a; size_t _size;//表示表中元素個數 }; //布隆過濾器 // 若判定不在布隆過濾器內就一定不在,若判定在布隆過濾器內則不一定存在 //可以刪除布隆過濾器內的元素,但必須採用引用計數設置布隆過濾器 template <class K> //使用搜索到的5種Hash函數 //http://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html struct _HashFunc1 { size_t DJBHash(const char *str) { if (!*str) return 0; register size_t hash = 5381; while (size_t ch = (size_t)*str++) { hash += (hash << 5) + ch; } return hash; } size_t operator()(const K& str) { return DJBHash(str.c_str()); } }; template <class K> struct _HashFunc2 { size_t SDBMHash(const char *str) { register size_t hash = 0; while (size_t ch=(size_t)*str++) { hash = 65599 * hash + ch; } return hash; } size_t operator()(const K& str) { return SDBMHash(str.c_str()); } }; template <class K> struct _HashFunc3 { size_t RSHash(const char *str) { register size_t hash = 0; size_t magic = 63689; while (size_t ch = (size_t)*str++) { hash = hash * magic + ch; magic *= 378551; } return hash; } size_t operator()(const K& str) { return RSHash(str.c_str()); } }; template <class K> struct _HashFunc4 { size_t APHash(const char *str) { register size_t hash = 0; size_t ch; for (long i = 0; ch = (size_t)*str++; i++) { if ((i & 1) == 0) { hash ^= ((hash << 7) ^ ch ^ (hash >> 3)); } else { hash ^= (~((hash << 11) ^ ch ^ (hash >> 5))); } } return hash; } size_t operator()(const K& str) { return APHash(str.c_str()); } }; template <class K> struct _HashFunc5 { size_t JSHash(const char *str) { if (!*str) return 0; register size_t hash = 1315423911; while (size_t ch = (size_t)*str++) { hash ^= ((hash << 5) + ch + (hash >> 2)); } return hash; } size_t operator()(const K& str) { return JSHash(str.c_str()); } }; size_t GetPrimeSize(size_t size) //求大於等於size的最小素數 { static const int _prime = 28; static const unsigned long _PrimeList[_prime] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (size_t i = 0; i < _prime; i++) { if (_PrimeList[i] >= size) { return _PrimeList[i]; } } return _PrimeList[_prime - 1]; } template<class K = string , class HashFunc1 = _HashFunc1<K> , class HashFunc2 = _HashFunc2<K> , class HashFunc3 = _HashFunc3<K> , class HashFunc4 = _HashFunc4<K> , class HashFunc5 = _HashFunc5<K>> class BloomFilter { public: BloomFilter(size_t size = 0) { _capacity = GetPrimeSize(size); _bm.ReSize(_capacity); } void Set(const K &key) { size_t index1 = HashFunc1()(key); size_t index2 = HashFunc2()(key); size_t index3 = HashFunc3()(key); size_t index4 = HashFunc4()(key); size_t index5 = HashFunc5()(key); _bm.Set(index1%_capacity); _bm.Set(index2%_capacity); _bm.Set(index3%_capacity); _bm.Set(index4%_capacity); _bm.Set(index5%_capacity); } bool Test(const K &key) { size_t index1 = HashFunc1()(key); size_t index2 = HashFunc2()(key); size_t index3 = HashFunc3()(key); size_t index4 = HashFunc4()(key); size_t index5 = HashFunc5()(key); if (!(_bm.Test(index1%_capacity))) return false; if (!(_bm.Test(index2%_capacity))) return false; if (!(_bm.Test(index3%_capacity))) return false; if (!(_bm.Test(index4%_capacity))) return false; if (!(_bm.Test(index5%_capacity))) return false; return true; } protected: BitMap _bm; size_t _capacity; };
void testFloomFiler() { BloomFilter<> bf(50); bf.Set("FootMart"); bf.Set("翔"); bf.Set("Happy"); bf.Set("http://10740329.blog.51cto.com/10730329/1772637#0-sqq-1-5364-9737f6f9e09dfaf5d3fd14d775bfee85"); bf.Set("http://baike.baidu.com/link?url=r_iijDxeb04ijVjBOONjFQDh6Cb0ceXRVHnt9xp1_0---j42HrYSl8ZjfZRnQ4r6eynyHj6RqVvMFUvmoupRla"); cout << " is emist? " << bf.Test("FootMart") << endl; cout << " is emist? " << bf.Test("翔") << endl; cout << " is emist? " << bf.Test("Happy1") << endl; cout << " is emist? " << bf.Test("http://10740329.blog.51cto.com/10730329/1772637#0-sqq-1-5364-9737f6f9e09dfaf5d3fd14d775bfee85") << endl; cout << " is emist? " << bf.Test("http://baike.baidu.com/link?url=r_iijDxeb04ijVjBOONjFQDh6Cb0ceXRVHnt9xp1_0---j42HrYSl8ZjfZRnQ4r6eynyHj6RqVvMFUvmoupRla2") << endl; }
布隆過濾器應用場景舉例:
(1)拼寫檢查、數據庫系統、文件系統
(2)假設要你寫一個網絡蜘蛛(web crawler)。由於網絡間的鏈接錯綜複雜,蜘蛛在網絡間爬行很可能會形成“環”。爲了避免形成“環”,就需要知道蜘蛛已經訪問過那些URL。給一個URL,怎樣知道蜘蛛是否已經訪問過呢?
(3)網絡應用
P2P網絡中查找資源操作,可以對每條網絡通路保存Bloom Filter,當命中時,則選擇該通路訪問。
廣播消息時,可以檢測某個IP是否已發包。
檢測廣播消息包的環路,將Bloom Filter保存在包裏,每個節點將自己添加入Bloom Filter。
信息隊列管理,使用Counter Bloom Filter管理信息流量。
(4)垃圾郵件地址過濾
像網易,QQ這樣的公衆電子郵件(email)提供商,總是需要過濾來自發送垃圾郵件的人(spamer)的垃圾郵件。一個辦法就是記錄下那些發垃圾郵件的email 地址。由於那些發送者不停地在註冊新的地址,全世界少說也有幾十億個發垃圾郵件的地址,將他們都存起來則需要大量的網絡服務器。如果用哈希表,每存儲一億個 email 地址,就需要1.6GB 的內存(用哈希表實現的具體辦法是將每一個email 地址對應成一個八字節的信息指紋,然後將這些信息指紋存入哈希表,由於哈希表的存儲效率一般只有50%,因此一個email 地址需要佔用十六個字節。一億個地址大約要1.6GB, 即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百GB 的內存。而Bloom Filter只需要哈希表1/8 到1/4 的大小就能解決同樣的問題。Bloom Filter決不會漏掉任何一個在黑名單中的可疑地址。而至於誤判問題,常見的補救辦法是在建立一個小的白名單,存儲那些可能別誤判的郵件地址。
(5)Bloomfilter在HBase中的作用
HBase利用Bloomfilter來提高隨機讀(Get)的性能,對於順序讀(Scan)而言,設置Bloomfilter是沒有作用的(0.92以後,如果設置了bloomfilter爲ROWCOL,對於指定了qualifier的Scan有一定的優化,但不是那種直接過濾文件,排除在查找範圍的形式)