最近在研究哈希表,写了闭散列和开散列的代码,哈希表的查找效率确实比较高,但是我发现哈希表的空间效率却有点低,一个好点的哈希表估计能达到50%的空间利用率吧。
我们知道STl中有一种数据结构叫做 bitset 个人觉得叫 bitmap 会更形象一点(这里不去关系这个了),位图是具有很高的空间效率的数据结构,在处理有些大数据的时候能够发挥出很大的作用。
看到这里应该懂我了吧,既然一个时间快,一个节约空间,那么你们在一起吧----------这就是布隆过滤器!一种时间和空间都比较高效的数据结构。布隆过滤器的设计原理是:使用 k 个哈希函数对同一个key进行定址,在bitmap中将各个哈希函数产生的index都置为1,这样就大大降低了哈希冲突(哈希冲突是不能完全避免的),此话怎讲呢?当我们去查找一个key是否存在的时候,也是通过使用一系列的哈希函数产生index,查看该index对应的位图位置是否为1,来判断该key是否出现过。结论如下:
1.若有哈希函数产生的 index 对应的位置不为 1 ,则说明该 key 没有出现过
2.若所有哈希函数产生的 index 对应的位置都为 1,我们可以推测该 key 有很大的概率出现过,因为哈希函数产出的 index 有一定的错误率的
布隆过滤器的应用还是比较广泛的,比如谷歌就使用到了布隆过滤来屏蔽骚扰邮箱,因为光光靠哈下表不能满足需求,举例说明一下吧,十亿个邮箱的URL所需要的存储空间大约16G吧,这么大的内存一般计算机是不能达到的,而使用布隆过滤器就能很好的解决问题了。不过布隆过滤器还是有缺点的,就是当bitmap中被置为1的位的个数接近总数目的时候,就容易产生错误,所以使用的时候要估计一下空间问题。
我实现了一个简单的布隆过滤器,用来判断string是否出现在其中,我使用了5个哈希函数来产生index,这些哈希函数都是一些经过测试的比较高效的字符哈希函数,我的项目结构如下:
其中包括了三个头文件BitMap,BloomFilter,commom:BitMap实现了一个位图,commom中包含了一些可以复用的字符串哈希函数,BloomFilter中实现布隆过滤器:
BitMap.h
#pragma once #include <iostream> #include <vector> using namespace std; class BitMap { public: BitMap(size_t size) { _array.resize((size>>5) + 1); } void Set(size_t num) { size_t index = num>>5; size_t pos = num % 32; _array[index] |= (1 << pos); } void Reset(size_t num) { size_t index = num>>5; size_t pos = num % 32; _array[index] &= ~(1 << pos); } bool Test(size_t num) { size_t index = num>>5; size_t pos = num % 32; return _array[index] & (1 << pos); } ~BitMap() {} private: vector<size_t> _array; };
/* * commom 头文件主要存放公共接口函数,提高代码的复用性,例如常见的字符哈系函数 * *create by admin-zou in 2015/5/13 * */ #pragma once //素数表 const int HashSize = 28; static const unsigned long PrimeList[HashSize] = { 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 }; size_t GetNextPrimeNum(size_t size) { for(size_t i = 0; i < HashSize; ++i) { if(size < PrimeList[i]) { return PrimeList[i]; } } return PrimeList[HashSize-1]; } // 字符哈系函数 template<class K> size_t BKDRHash(const K *str) { register size_t hash = 0; while (size_t ch = (size_t)*str++) { hash = hash * 131 + ch; // 也可以乘以31、131、1313、13131、131313.. } return hash; } template<class K> struct hashFunc1 { size_t operator() (const char* key) { return BKDRHash(key); } }; template<class K> size_t SDBMHash(const K *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; } template<class K> struct hashFunc2 { size_t operator() (const char* key) { return SDBMHash(key); } }; template<class K> size_t RSHash(const K *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; } template<class K> struct hashFunc3 { size_t operator() (const char* key) { return RSHash(key); } }; template<class K> size_t APHash(const K *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; } template<class K> struct hashFunc4 { size_t operator() (const char* key) { return APHash(key); } }; template<class K> size_t JSHash(const K *str) { if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0 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 hashFunc5 { size_t operator() (const char* key) { return JSHash(key); } };
BloomFilter.h
/* 布隆过滤器 * * Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对bit-map 的扩展, 它的原理是: *当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了: * 如果这些点有任何一个 0,则被检索元素一定不在; * 如果都是 1,则被检索元素很可能在。 * * create by admin-zou in 2016/5/13 */ #pragma once #include <iostream> #include <string> #include "BitMap.h" #include "common.h" using namespace std; template <class K, class HashFuncer1 = hashFunc1<K>, class HashFuncer2 = hashFunc2<K>, class HashFuncer3 = hashFunc3<K>, class HashFuncer4 = hashFunc4<K>, class HashFuncer5 = hashFunc5<K> > class BloomFiter { public: BloomFiter(size_t size) :_capacity(GetNextPrimeNum(size)) ,_bt(_capacity) {} void Add(const K& key) { size_t index1 = HashFuncer1()(key.c_str()) % _capacity; _bt.Set(index1); //cout<<index1<<endl; size_t index2 = HashFuncer2()(key.c_str()) % _capacity; _bt.Set(index2); //cout<<index2<<endl; size_t index3 = HashFuncer3()(key.c_str()) % _capacity; _bt.Set(index3); //cout<<index3<<endl; size_t index4 = HashFuncer4()(key.c_str()) % _capacity; _bt.Set(index4); //cout<<index4<<endl; size_t index5 = HashFuncer5()(key.c_str()) % _capacity; _bt.Set(index5); //cout<<index5<<endl; } bool Check(const K& key) { size_t index1 = HashFuncer1()(key.c_str()) % _capacity; //cout<<index1<<endl; if(! _bt.Test(index1)) { return false; } size_t index2 = HashFuncer2()(key.c_str()) % _capacity; //cout<<index2<<endl; if(! _bt.Test(index2)) { return false; } size_t index3 = HashFuncer3()(key.c_str()) % _capacity; //cout<<index3<<endl; if(! _bt.Test(index3)) { return false; } size_t index4 = HashFuncer4()(key.c_str()) % _capacity; //cout<<index4<<endl; if(! _bt.Test(index4)) { return false; } size_t index5 = HashFuncer5()(key.c_str()) % _capacity; //cout<<index5<<endl; if(! _bt.Test(index5)) { return false; } return true; } private: size_t _capacity; BitMap _bt; };
--Just do IT