布隆过滤器

目录

1. 布隆过滤器的概念

2. 布隆过滤器的应用

3. 布隆过滤器的实现

4. 布隆过滤器的总结

5. 布隆过滤器的拓展

5.1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?

5.2.  给一个超过100G大小的 log file,log 中存着IP地址,设计算法找到出现次数最多的IP地址?如何找到top K的IP? 


1. 布隆过滤器的概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉 "某样东西一定不存在或者可能存在",它是用多个哈希函数,将一个数据映射到位图结构中,此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

布隆过滤器的设计思路

我们发现,上面的映射关系可能不具备唯一性。

也就是说,当我们去判断某一个值存不存在的时候,可能具有误判,假设上面没有"野牛"这个字符串,但是当我们去判断"野牛"存不存在的时候,如果此时"大熊猫"是存在的,那么我们也会得到"野牛"是存在的结果,而如果此时"大熊猫"也不存在,那么我们会得到"野牛"是一定不存在的。

因此:

  • 对于在的判断,是不准确的,存在误判;
  • 但是对于不在的判断,是准确的,具有唯一性。

而对于这种误判,我们是不能做到完全杜绝的,但是我们却可以降低它的误判率。

那么如何降低误判率呢?我们可以让每个Key多映射几个位,如下图:

虽然此时,"野牛"和"大熊猫"有一个位置冲突了,但是它们剩余的位置却是不冲突的,即只有两个字符串所有映射的位置都冲突了,才会导致误判,因此,在一定程度上降低了误判率。

理论而言:

一个值映射的位越多,误判概率越低,但是也不敢映射太多,映射位太多,那么空间消耗就越大,优势就会被削弱。

而上面的方式,也就是布隆过滤器的大致实现思路。

2. 布隆过滤器的应用

布隆过滤器(Bloom Filter)是一种空间效率非常高的概率数据结构,用于判断一个元素是否属于一个集合。它可以用于快速过滤掉不属于集合中的元素,具有高效的查询速度和较小的内存占用。

布隆过滤器的应用场景包括但不限于以下几个方面:

  • 1. 缓存:在缓存系统中,布隆过滤器可以用来判断一个待查询的数据是否存在于缓存中,从而避免对底层数据存储系统的查询操作,提高缓存的命中率。
  • 2. 数据库查询优化:在数据库系统中,布隆过滤器可以用于减少对磁盘或网络的查询压力。例如,在查询之前,可以先使用布隆过滤器判断某个查询条件是否存在对应的记录,如果不存在,可以快速返回查询结果为空,避免不必要的查询操作。
  • 3. 网络爬虫去重:在网络爬虫系统中,布隆过滤器可以用于去重操作。爬虫在爬取网页时,可以使用布隆过滤器来过滤已经抓取过的网页链接,避免重复抓取相同的内容。
  • 4. 防止缓存穿透:在分布式系统中,布隆过滤器可以用于防止缓存穿透。当一个请求的查询结果不存在于缓存中时,可以先通过布隆过滤器进行快速判断,如果查询结果不存在于布隆过滤器中,可以直接拒绝该请求,避免对底层存储系统的过度查询负载。

需要注意的是,布隆过滤器在判断元素是否存在时,可能会存在一定的误判率(False Positive)。因此,在使用布隆过滤器时需要权衡误判率和内存占用,根据实际需求选择合适的参数配置和误判率控制策略。

由于黑名单的Key是整体的一小部分,而大多数的Key都不在黑名单,因此布隆过滤器可以快速过滤掉黑名单中不存在的Key,避免过多的操作,进而提高效率。 

3. 布隆过滤器的实现

在开始实现之前,我们应该解决一个问题: 如何选择哈希函数个数布隆过滤器长度,人们经过分析和实践,得出下面的结论:

在这里我们粗略计算,当 k = 3 时,插入1个元素,大概需要5个空间。

那么我们的实现如下:

class hash1_string      //BKDRHash
{
public:
	size_t operator()(const std::string& str)
	{
		size_t ret = 0;
		for (auto ch : str)
		{
			ret *= 131;
			ret += ch;
		}
		return ret;
	}
};

class hash2_string   //APHash
{
public:
	size_t operator()(const std::string& str)
	{
		size_t hash = 0;
		size_t ch = 0;
		for (size_t i = 0;i < str.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

class hash3_string   //DJBHash
{
public:
	size_t operator()(const std::string& str)
	{
		size_t hash = 5381;
		for (auto ch : str)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};
// 布隆过滤器大部分情况Key都是string
template<size_t N,class K = std::string,class Hash1 = hash1_string,class Hash2 = hash2_string,class Hash3 = hash3_string>
class bloom_filter
{
public:
	void set(const K& key)
	{
		// 第一个哈希函数映射的位置
		size_t hash_index1 = Hash1()(key) % (_ratio * 5);
		_table.set(hash_index1);
		// 第二个哈希函数映射的位置
		size_t hash_index2 = Hash2()(key) % (_ratio * 5);
		_table.set(hash_index2);
		// 第三个哈希函数映射的位置
		size_t hash_index3 = Hash3()(key) % (_ratio * 5);
		_table.set(hash_index3);
	}

	bool test(const K& key)
	{
		size_t hash_index1 = Hash1()(key) % (_ratio * 5);
		if (!_table.test(hash_index1))  // 不存在是确定的
			return false;   
		size_t hash_index2 = Hash2()(key) % (_ratio * 5);
		if (!_table.test(hash_index2))  // 不存在是确定的
			return false;
		size_t hash_index3 = Hash3()(key) % (_ratio * 5);
		if (!_table.test(hash_index3))  // 不存在是确定的
			return false;
		return true;  // 走到这里,说明可能存在,但不确定,可能误判
	}

private:
	const static size_t _ratio = 5;     // 当哈希函数个数为3,插入一个数据,需要5个空间
	bit_set<N*_ratio> _table;
};

布隆过滤器不能直接支持删除,因为某些Key可能会映射到同一个位置,删除会影响其他Key。

一种支持删除的方法:采用引用计数的思想 ,将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器 (k个哈希函数计算出的哈希地址) 加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。

缺陷:

  • 无法确认元素是否真正在布隆过滤器中,存在误判;
  • 存在计数回绕。

4. 布隆过滤器的总结

布隆过滤器优点:

  • 增加和查询元素的时间复杂度为:O(K),(K为哈希函数的个数,一般比较小),与数据量大小无关;
  • 哈希函数相互之间没有关系,方便硬件并行运算;
  • 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势;
  • 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势;
  • 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能;
  • 使用同一组散列函数的布隆过滤器可以进行交、并、差运算。

布隆过滤器缺点:

  • 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据);
  • 不能获取元素本身;
  • 一般情况下不能从布隆过滤器中删除元素;
  • 如果采用计数方式删除,可能会存在计数回绕问题。

5. 布隆过滤器的拓展

5.1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?

在这里我们采用一种思想:哈希切分,大概思路如下:

注意:这里的分割并不是平均切分,而是根据 Key 取模后得到不同的值进入不同的文件。

5.2.  给一个超过100G大小的 log file,log 中存着IP地址,设计算法找到出现次数最多的IP地址?如何找到top K的IP? 

关键点:虽然一个文件中可能有不同的ip,但是相同的ip一定在同一个文件。

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值