c++实现Bloom Filter

Bloom Filter(BF) 是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法,用于快速查找某个元素是否属于集合, 但不要求百分百的准确率。 Bloom filter通常用于爬虫的url去重,即判断某个url是否已经被爬过。 

为了说明Bloom Filter存在的重要意义,举一个实例:

  假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案:

  1. 将访问过的URL保存到数据库。

  2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。

  3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。

  4. Bit-Map方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。

  方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。

  以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。

  方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了?

  方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。

  方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。

  方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。

  实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。

(1)哈希函数选择

  哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。

(2)Bit数组大小选择

  哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以参考参考文献1。该文献证明了对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。

  同时该文献还给出特定的k,m,n的出错概率。例如:根据参考文献,哈希函数个数k取10,位数组大小m设为字符串个数n的20倍时,false positive发生的概率是0.0000889 ,这个概率基本能满足网络爬虫的需求了。

c++实现实例:

#include <iostream>
#include <cstdio>
#include <bitset>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <cstring>

using namespace std;
using std::bitset;

typedef unsigned int uint;
#define N 1<<20

int array[] = {5, 7, 11, 13, 31, 37, 61};
class BloomFilter
{
public:
    BloomFilter(int m, int n):m_m(m), m_n(n)
	{
		m_k = ceil((m/n) * log(2));
	}
    virtual ~BloomFilter()
	{}
	
	uint RSHash(const char *str, int seed);
	void SetKey(const char* str);
	int VaryExist(const char* str);

private:
	int m_k, m_m, m_n; //k:number of the hash functions //m:the size of bitset //n:number of strings to hash (k = [m/n]*ln2)
	bitset<N> bit;
};


uint BloomFilter::RSHash(const char *str, int seed)    
{    
    //unsigned int b = 378551;    
    uint a = 63689;
    uint hash = 0;    
     
    while (*str)    
    {    
        hash = hash * a + (*str++);    
        a *= seed;    
    }    
     
    return (hash & 0x7FFFFFFF);    
}    

void BloomFilter::SetKey(const char* str)
{
	int *p = new int[m_k+1];
	memset(p, 0, sizeof(p)/sizeof(int));
	for(int i=0;i<m_k;++i)
	{
		p[i] = static_cast<int>(RSHash(str, array[i]))%1000000;
	}

	for(int j=0;j<m_k; ++j)
	{
		bit[p[j]] = 1;
	}
	delete[] p;
}

int BloomFilter::VaryExist(const char* str)
{
	int res = 1;
	int *p = new int[m_k+1];
	memset(p, 0, sizeof(p)/sizeof(int));
	for(int i=0;i<m_k;++i)
	{
		p[i] = static_cast<int>(RSHash(str, array[i]))%1000000;
	}

	for(int j=0;j<m_k;++j)
	{
		res &= bit[p[j]];
		if(!res)
		{
			delete[] p;
			return 0;
		}			
	}
	delete[] p;
	return 1;
}

int main(int argc, char *argv[])
{
	BloomFilter bf(5, 2);
    string str = "hahahehe";
	string str2 = "sdfasfa";
	bf.SetKey(str.c_str());
	int res = bf.VaryExist(str.c_str());

	if(res)
		cout << "exist" << endl;
	else
		cout << "not exist" << endl;

	//bf.SetKey(str2.c_str());
	res = bf.VaryExist(str2.c_str());
	if(res)
		cout << "exist" << endl;
	else
		cout << "not exist" << endl;
	
    return 0;
}






  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值