布隆過濾器
假如現在有40億個ip地址(string類型),然後給你一個ip地址,讓你查找這個ip地址在不在這40億個ip地址裏?我們應該怎麼做呢?
- 如果用哈希表來處理的話,這裏有40億的數據,數據量太大,因此太佔用空間
- 如果用位圖來處理的話,這裏因爲是字符串,有可能不同的字符串映射的是同一個位,會有哈希衝突的問題,導致誤判
- 因此我們採用的是哈希+位圖的方法,也就是布隆過濾器
布隆過濾器是由布隆(Burton Howard Bloom)在1970年提出的一種緊湊型的、比較巧妙的概率型數據結構,特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”,它是用多個哈希函數,將一個數據映射到位圖結構中。
布隆過濾器的優點:
- 查詢效率高:使用了哈希的思想,一個數據映射了多個位
- 節省大量的內存空間:採用了位圖
布隆過濾器的缺點:
布隆過濾器如果判斷出該數據沒有,那麼這個數據一定沒有,因爲只要有一個映射的位不存在時,那麼該數據就不存在。但是布隆過濾器如果判斷出該數據有,那麼其實是有可能出現誤差的,因爲字符串的計算難免會導致某兩個字符串映射的幾個位置都一樣,因爲我們寫的哈希映射算法是固定的,字符串的隨機排列組合是無限多的,所以如果兩個字符串計算出來的映射的位一樣時,就有可能導致誤判。(也就是說這個字符串並不存在,但是由於映射的位和之前的字符串相同,所以就會判斷出這個字符串也存在)
布隆過濾器的實現
布隆過濾器支持set接口,test接口,可以複用位圖的set和test接口。但是布隆過濾器不能支持刪除,因爲通過多個哈希算法一個值會映射多個位,而且不同的值有可能會映射相同的位,如果刪除的這個值所對應的位會變成0,那如果其他的值也映射這個位的話,就會導致其他的那個值被判定爲不存在,但其實你並沒有刪除它。因此不支持刪除。
那如果一定要實現刪除操作呢?那就只能使用引用計數,但是採用引用計數刪除的話就需要進行儲存這個計數,需要額外的空間,會增大佔用內存的大小。使用引用計數就是有多個值同時映射這個位,就把引用計數++,刪除時就–,然後判斷是否存在則是看值是否大於0。
具體代碼如下:
BloomFilter.h
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "bitset.h"
using namespace std;
template <class K, class HashFunc1, class HashFunc2, class HashFunc3>
class BloomFilter
{
public:
BloomFilter(size_t keynum)//開多少位呢?
:_bs(keynum * 5)
{}
void set(const K& key)
{
//如果是字符串,假如開了50個位,那麼你映射的位不一定就在這50個位之間,因此要模上bitnum
//使計算出來的位都在這50個位裏進行映射
size_t index1 = HashFunc1()(key) % _bs.GetBitNum();
size_t index2 = HashFunc2()(key) % _bs.GetBitNum();
size_t index3 = HashFunc3()(key) % _bs.GetBitNum();
_bs.set(index1);
_bs.set(index2);
_bs.set(index3);
}
//void reset(const K& key)//布隆過濾器不支持reset操作,因爲同一個位可能被不同的字符串所映射
bool test(const K& key)
{
size_t index1 = HashFunc1()(key) % _bs.GetBitNum();
if (_bs.test(index1) == false)
return false;
size_t index2 = HashFunc2()(key) % _bs.GetBitNum();
if (_bs.test(index2) == false)
return false;
size_t index3 = HashFunc3()(key) % _bs.GetBitNum();
if (_bs.test(index3) == false)
return false;
return true;//有可能會存在誤判的情況
}
private:
bitset _bs;//位圖
};
//字符串哈希算法
struct StrHash1
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash *= 131;
hash += e;
}
return hash;
}
};
struct StrHash2
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash *= 65599;
hash += e;
}
return hash;
}
};
struct StrHash3
{
size_t operator()(const string& s)
{
size_t hash = 0;
size_t magic = 63689;
for (auto e : s)
{
hash *= magic;
hash += e;
magic *= 378551;
}
return hash;
}
};
void test_BloomFilter()
{
BloomFilter<string, StrHash1, StrHash2, StrHash3> bf(10);
cout << StrHash1()("https://mail.qq.com") << endl;
cout << StrHash2()("https://mail.qq.com") << endl;
cout << StrHash3()("https://mail.qq.com") << endl;
string url1("https://mail.qq.com1");
string url2("https://mail.qq.com2");
string url3("https://mail.qq.com3");
string url4("http://mail.qq.com");
string url5("https://maill.qq.com");
bf.set(url1);
bf.set(url2);
bf.set(url3);
cout << bf.test(url1) << endl;
cout << bf.test(url2) << endl;
cout << bf.test(url3) << endl;
cout << bf.test(url4) << endl;
cout << bf.test(url5) << endl;
}
bitset.h
#pragma once
#include <iostream>
#include <vector>
using namespace std;
class bitset
{
public:
bitset(size_t bitnum)//開多少位
:_bitnum(bitnum)
{
_bit.resize((bitnum >> 3) + 1, 0);//除8+1是確定數組開多大,數組中的每一個元素都可以標識8個數據
}
void set(size_t x)
{
//size_t index = x / 8;//計算是數組的第幾個段
size_t index = x >> 3;
size_t num = x % 8;//計算是這個段的第幾個位
//把對應的比特位置爲1
_bit[index] |= (1 << num);
}
void reset(size_t x)
{
size_t index = x >> 3;
size_t num = x % 8;
//把對應的比特位置爲0
_bit[index] &= (~(1 << num));
}
bool test(size_t x)
{
//判斷對應位是0還是1
size_t index = x >> 3;
size_t num = x % 8;
return _bit[index] & (1 << num);//與完之後的結果如果非零就說明存在,爲零說明不存在
}
size_t GetBitNum()
{
return _bitnum;
}
private:
vector<char> _bit;
size_t _bitnum;
};
test.cpp
#include "BloomFilter.h"
int main()
{
test_BloomFilter();
system("pause");
return 0;
}