【布隆過濾器】實現一個簡單的布隆過濾器
原理
布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進位制向量和一系列隨機對映函式。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。
Bloom Filter 是一種空間效率很高的隨機資料結構,Bloom filter 可以看做是對 bit-map 的擴充套件, 它的原理是:
當一個元素被加入集合時,通過 K 個 Hash 函式將這個元素對映成一個位陣列(Bit array)中的 K 個點,把它們置為 1。檢索時,我們只要看看這些點是不是都是 1 就(大約)知道集合中有沒有它了:
- 如果這些點有任何一個 0,則被檢索元素一定不在;
- 如果都是 1,則被檢索元素很可能在。
優缺點
優點
它的優點是空間效率和查詢時間都遠遠超過一般的演算法,布隆過濾器儲存空間和插入 / 查詢時間都是常數O(k)。另外, 雜湊函式相互之間沒有關係,方便由硬體並行實現。布隆過濾器不需要儲存元素本身,在某些對保密要求非常嚴格的場合有優勢。
缺點
但是布隆過濾器的缺點和優點一樣明顯。誤算率是其中之一。隨著存入的元素數量增加,誤算率隨之增加。但是如果元素數量太少,則使用散列表足矣。
(誤判補救方法是:再建立一個小的白名單,儲存那些可能被誤判的資訊。)
另外,一般情況下不能從布隆過濾器中刪除元素. 我們很容易想到把位陣列變成整數陣列,每插入一個元素相應的計數器加 1, 這樣刪除元素時將計數器減掉就可以了。然而要保證安全地刪除元素並非如此簡單。首先我們必須保證刪除的元素的確在布隆過濾器裡面. 這一點單憑這個過濾器是無法保證的。另外計數器迴繞也會造成問題。
應用
布隆過濾器在很多場合能發揮很好的效果,比如:網頁URL的去重,垃圾郵件的判別,集合重複元素的判別,查詢加速(比如基於key-value的儲存系統)等,下面舉幾個例子:
url去重
A,B 兩個檔案,各存放 50 億條 URL,每條 URL 佔用 64 位元組,記憶體限制是 4G,讓你找出 A,B 檔案共同的 URL。如果是三個乃至 n 個檔案呢?
分析 :如果允許有一定的錯誤率,可以使用 Bloom filter,4G 記憶體大概可以表示 340 億 bit。將其中一個檔案中的 url 使用 Bloom filter 對映為這 340 億 bit,然後挨個讀取另外一個檔案的 url,檢查是否與 Bloom filter,如果是,那麼該 url 應該是共同的 url(注意會有一定的錯誤率)。”
垃圾郵件
假定我們儲存一億個電子郵件地址,我們先建立一個十六億二進位制(位元),即兩億位元組的向量,然後將這十六億個二進位制全部設定為零。對於每一個電子郵件地址 X,我們用八個不同的隨機數產生器(F1,F2, …,F8) 產生八個資訊指紋(f1, f2, …, f8)。再用一個隨機數產生器 G 把這八個資訊指紋對映到 1 到十六億中的八個自然數 g1, g2, …,g8。現在我們把這八個位置的二進位制全部設定為一。當我們對這一億個 email 地址都進行這樣的處理後。一個針對這些 email 地址的布隆過濾器就建成了。
實現布隆過濾器
common.h檔案中
/雜湊函式
template<class K>
class HashFunDef
{
public:
size_t operator()(const K& key)
{
return key;
}
};
//string轉化為數字
static size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
template<>
class HashFunDef<string>
{
public:
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());
}
};
size_t SDBMHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch+(hash<<6)+ (hash<<16)-hash;
}
return hash;
}
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 APHash(const char* str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if (0 == (i & 1))
{
hash ^= ((hash << 7) ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
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;
}
template<class K>
struct __HashFunc1
{
size_t operator()(const K& key)
{
return BKDRHash(key.c_str());
}
};
template<class K>
struct __HashFunc2
{
size_t operator()(const K& key)
{
return SDBMHash(key.c_str());
}
};
template<class K>
struct __HashFunc3
{
size_t operator()(const K& key)
{
return RSHash(key.c_str());
}
};
template<class K>
struct __HashFunc4
{
size_t operator()(const K& key)
{
return APHash(key.c_str());
}
};
template<class K>
struct __HashFunc5
{
size_t operator()(const K& key)
{
return JSHash(key.c_str());
}
};
實現布隆過濾器
template<class K, class HashFunc = __HashFunc1<string>>
class BloomFile
{
public:
BloomFile(size_t size)
:_map(size)
{}
bool Insert(string str)
{
size_t idx1 = __HashFunc1()(str);
size_t idx2 = __HashFunc2()(str);
size_t idx3 = __HashFunc3()(str);
size_t idx4 = __HashFunc4()(str);
size_t idx5 = __HashFunc5()(str);
_map.Set(idx1); _map.Set(idx2);
_map.Set(idx3); _map.Set(idx4);
_map.Set(idx5);
}
bool Find(string str)
{
size_t idx1 = __HashFunc1()(str);
size_t idx2 = __HashFunc2()(str);
size_t idx3 = __HashFunc3()(str);
size_t idx4 = __HashFunc4()(str);
size_t idx5 = __HashFunc5()(str);
if (!_map.Test(idx1))
return false;
if (!_map.Test(idx2))
return false;
if (!_map.Test(idx3))
return false;
if (!_map.Test(idx4))
return false;
if (!_map.Test(idx5))
return false;
return true;
}
private:
BitMap _map;
};