0. 科普
1. 為什么需要Bloom Filter
2. 基本原理
3. 如何設計Bloom Filter
4. 實例操作
5. 擴展
0. 科普
Bloom Filter是由Bloom在1970年提出的一種多哈希函數映射的快速查找算法。通常應用在一些需要快速判斷某個元素是否屬於集合,但是並不嚴格要求100%正確的場合。
1. 為什么需要Bloom Filter
舉例說明:假設有2000萬個url,現在判斷一個新的url是否在這2000萬個之中。可以有的思路:
- 將訪問過的URL保存到數據庫。
- 用HashSet將訪問過的URL保存起來。那只需接近O(1)的代價就可以查到一個URL是否被訪問過了。
- URL經過MD5等單向哈希后再保存到HashSet或數據庫。
- Bit-Map方法。建立一個BitSet,將每個URL經過一個哈希函數映射到某一位。
分析
思路1:當數據量很大時,查詢數據庫變得效率底下
思路2:太消耗內存,還得把字符串全部儲存起來
思路3:字符串經過MD5處理后有128個bit,比思路2省了很多空間
思路4:一個字符串僅用一位來表示,比思路3還節省空間
當然前提是會出現誤判(哈希后表示相同),為了繼承這么好的思路,同時減少誤判的情況,可以來個折衷:一個哈希函數生成一個位,用多個哈希函數生成多個位來存儲一個字符串。這樣比Bit-Map多用了些空間,但是減少了誤判率。
2. 基本原理
這樣把大量的字符串存起來。查找時,用同樣的哈希處理待查串,如果對應的各位上都為1,說明該字符串可能在這些字符串中,否則一定不在其中。
3. 如何設計Bloom Filter
如何降低誤判率是關鍵,這需要
- 選取區分度高的哈希函數
- 根據存儲數組、哈希函數個數、誤判率之間的關系,分配空間、個數
直接利用前人的結論:
其中f'是自己期望的誤判率,m是總共開辟的存儲空間位數,n是待存儲字符串的個數,k是哈希函數的個數,f是真正的誤判率。
4. 實例操作
需求:2000萬個已知url,100個待查url
設計:
1. 設定誤判率為0.1, n=2000萬,計算
m = n * 1.44 * math.log(1/f)/math.log(2)=287014588 k = 0.693 * m / n= 10 f = (1 - math.exp(-1 * k * n / m)) ** k = 0.00101298781512
哈希函數的選取看這里
參考代碼(c++)
makefile

objects = main.o hash.o bloomfilter.o main : $(objects) g++ -o main $(objects) main.o : hash.h bloomfilter.h bloomfilter.o : bloomfilter.h hash.o : hash.h clean: rm *.o main
main.cc

#include <iostream> #include <fstream> #include <sstream> #include <cstring> #include <string> #include "bloomfilter.h" using namespace std; const int MAXSIZE = 400000000; int main(int argc, char **argv) { char *poolfile = argv[1]; char *testfile = argv[2]; cout << poolfile << endl; ifstream fpool(poolfile); ifstream ftest(testfile); if(!fpool) { cout << "cannot open the file:" << poolfile << endl; return 0; } if(!ftest) { cout << "cannot open the file:" << testfile << endl; return 0; } BloomFilter bf(MAXSIZE); bf.setBit(fpool); cout << "Store OK" << endl; bf.checkBit(ftest); cout << "Check OK" << endl; fpool.close(); ftest.close(); }
bloomfilter.h

#include <iostream> #include <string> #include <fstream> #include <vector> #include "hash.h" using namespace std; class BloomFilter { public: BloomFilter(int size) : SIZE(size) { vec.resize(size); }; void setBit(ifstream &f); void setBit(const string &s); void setBit(unsigned int count); bool checkBit(ifstream &f); bool checkBit(const string &s); bool checkBit(unsigned int count); private: vector<char> vec; const unsigned int SIZE; };
bloomfilter.cc

#include "bloomfilter.h" void BloomFilter::setBit(ifstream &f) { string line; while(f >> line) { setBit(line); } } bool BloomFilter::checkBit(ifstream &f) { string line; while(f >> line) { if(!checkBit(line)) cout << line << endl; } } void BloomFilter::setBit(const string &s) { unsigned int bitpos = 0; const char *str = s.c_str(); int len = s.size(); bitpos = RSHash(str, len); setBit(bitpos); bitpos = JSHash(str, len); setBit(bitpos); bitpos = PJWHash(str, len); setBit(bitpos); bitpos = ELFHash(str, len); setBit(bitpos); bitpos = BKDRHash(str, len); setBit(bitpos); bitpos = SDBMHash(str, len); setBit(bitpos); bitpos = DJBHash(str, len); setBit(bitpos); bitpos = DEKHash(str, len); setBit(bitpos); bitpos = BPHash(str, len); setBit(bitpos); bitpos = FNVHash(str, len); setBit(bitpos); } bool BloomFilter::checkBit(const string &s) { unsigned int bitpos = 0; const char *str = s.c_str(); int len = s.size(); bool rev = true; bitpos = RSHash(str, len); rev &= checkBit(bitpos); bitpos = JSHash(str, len); rev &= checkBit(bitpos); bitpos = PJWHash(str, len); rev &= checkBit(bitpos); bitpos = ELFHash(str, len); rev &= checkBit(bitpos); bitpos = BKDRHash(str, len); rev &= checkBit(bitpos); bitpos = SDBMHash(str, len); rev &= checkBit(bitpos); bitpos = DJBHash(str, len); rev &= checkBit(bitpos); bitpos = DEKHash(str, len); rev &= checkBit(bitpos); bitpos = BPHash(str, len); rev &= checkBit(bitpos); bitpos = FNVHash(str, len); rev &= checkBit(bitpos); return rev; } void BloomFilter::setBit(unsigned int count) { count = count % (SIZE * 8); vec[count / 8] |= (1 << (count % 8)); } bool BloomFilter::checkBit(unsigned int count) { count = count % (SIZE * 8); return vec[count / 8] &= (1 << (count % 8)); }
hash.h

unsigned int RSHash(const char* str, unsigned int len); unsigned int JSHash(const char* str, unsigned int len); unsigned int PJWHash(const char* str, unsigned int len); unsigned int ELFHash(const char* str, unsigned int len); unsigned int BKDRHash(const char* str, unsigned int len); unsigned int SDBMHash(const char* str, unsigned int len); unsigned int DJBHash(const char* str, unsigned int len); unsigned int DEKHash(const char* str, unsigned int len); unsigned int BPHash(const char* str, unsigned int len); unsigned int FNVHash(const char* str, unsigned int len);
hash.cc

#include "hash.h" unsigned int RSHash(const char* str, unsigned int len) { unsigned int b = 378551; unsigned int a = 63689; unsigned int hash = 0; unsigned int i = 0; for(i=0; i<len; str++, i++) { hash = hash*a + (*str); a = a*b; } return hash; } /* End Of RS Hash Function */ unsigned int JSHash(const char* str, unsigned int len) { unsigned int hash = 1315423911; unsigned int i = 0; for(i=0; i<len; str++, i++) { hash ^= ((hash<<5) + (*str) + (hash>>2)); } return hash; } /* End Of JS Hash Function */ unsigned int PJWHash(const char* str, unsigned int len) { const unsigned int BitsInUnsignedInt = (unsigned int)(sizeof(unsigned int) * 8); const unsigned int ThreeQuarters = (unsigned int)((BitsInUnsignedInt * 3) / 4); const unsigned int OneEighth = (unsigned int)(BitsInUnsignedInt / 8); const unsigned int HighBits = (unsigned int)(0xFFFFFFFF) << (BitsInUnsignedInt - OneEighth); unsigned int hash = 0; unsigned int test = 0; unsigned int i = 0; for(i=0;i<len; str++, i++) { hash = (hash<<OneEighth) + (*str); if((test = hash & HighBits) != 0) { hash = ((hash ^(test >> ThreeQuarters)) & (~HighBits)); } } return hash; } /* End Of P. J. Weinberger Hash Function */ unsigned int ELFHash(const char* str, unsigned int len) { unsigned int hash = 0; unsigned int x = 0; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = (hash << 4) + (*str); if((x = hash & 0xF0000000L) != 0) { hash ^= (x >> 24); } hash &= ~x; } return hash; } /* End Of ELF Hash Function */ unsigned int BKDRHash(const char* str, unsigned int len) { unsigned int seed = 131; /* 31 131 1313 13131 131313 etc.. */ unsigned int hash = 0; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = (hash * seed) + (*str); } return hash; } /* End Of BKDR Hash Function */ unsigned int SDBMHash(const char* str, unsigned int len) { unsigned int hash = 0; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = (*str) + (hash << 6) + (hash << 16) - hash; } return hash; } /* End Of SDBM Hash Function */ unsigned int DJBHash(const char* str, unsigned int len) { unsigned int hash = 5381; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = ((hash << 5) + hash) + (*str); } return hash; } /* End Of DJB Hash Function */ unsigned int DEKHash(const char* str, unsigned int len) { unsigned int hash = len; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = ((hash << 5) ^ (hash >> 27)) ^ (*str); } return hash; } /* End Of DEK Hash Function */ unsigned int BPHash(const char* str, unsigned int len) { unsigned int hash = 0; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash = hash << 7 ^ (*str); } return hash; } /* End Of BP Hash Function */ unsigned int FNVHash(const char* str, unsigned int len) { const unsigned int fnv_prime = 0x811C9DC5; unsigned int hash = 0; unsigned int i = 0; for(i = 0; i < len; str++, i++) { hash *= fnv_prime; hash ^= (*str); } return hash; } /* End Of FNV Hash Function */
數據下載:http://pan.baidu.com/s/1hqBTks0
Github 地址:https://github.com/jihite/Bloom-Filter
5. 擴展
如何刪除存儲數組中的元素?
思路:把存儲數組的每一個元素擴展一下(原來是1b)用來存儲該位置被置1的次數。存儲是,計數次數加一;刪除的時候,計數次數減一。