【数据结构】—— 哈希的应用之布隆过滤器

BitMap

上篇博客我们讲到了位图(BitMap):哈希应用之位图 ,但是位图不是万能的,如我们需要存储的64bit类型的数据,还能不能用BitMap?我们来算一算:
在这里插入图片描述
EB(Exabyte,艾字节)这个计算机科学中统计数据量的单位有多大,有兴趣的小伙伴可以查阅下资料。这个量级的BitMap,已经不是人类硬件所能承担的了。我相信谁也不会想用集群去计算这么一个问题吧?所以BitMap的好处在于空间复杂度不随原始集合内元素的个数增加而增加,而它的坏处也源于这一点 —— 空间复杂度随集合内最大元素增大而线性增大

所以接下来,我们要引入另一个著名的工业实现——布隆过滤器(Bloom Filter)。

布隆过滤器概念

布隆过滤器
  • 布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
  • 如果说BitMap对于每一个可能的整型值,通过直接寻址的方式进行映射,相当于使用了一个哈希函数,那布隆过滤器就是引入了k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。下图中是k = 3k = 3时的布隆过滤器。
    【数据结构】—— 哈希的应用之布隆过滤器_第1张图片
  • x,y,z 经由哈希函数映射将各自在BitMap中的3个位置的值置为1,当w出现时,仅当3个标志位都为1时,才表示w在集合中。图中所示的情况,布隆过滤器将判定w不在集合中。
BloomFilter的核心思想:
  • 多个HashFunc,增大随机性,减少哈希冲突的概率。
  • 扩大数组范围,使哈希值均匀分布,进一步减少哈希冲突的概率。
BloomFilter的准确性

尽管BloomFilter已经尽可能的减小哈希冲突的概率了,但是,并不能彻底消除,因此正如上面提到的:

  • 如果对应的bit位值都为1,那么也不能肯定这个值一定存在
  • 也就是说,BloomFilter其实是存在一定的误判的,这个误判的概率显然和数组的大小以及HashFunc的个数以及每个HashFunc本身的好坏有关,具体的计算公式,可以查阅相关论文。
  • 关于BloomFilter的准确性可参考博客:BloomFilter的效率
    下图是布隆过滤器假正例概率 p 与位数组大小 m 和集合中插入元素个数 n 的关系图,假定 Hash 函数个数选取最优数目:K = (m/n)ln2
    【数据结构】—— 哈希的应用之布隆过滤器_第2张图片

布隆过滤器的简单实现

布隆结构
  • 设计布隆过滤器结构,假设我们使用3个哈希函数进行映射(哈希函数个数可自己调整)
  • 一般来说哈希函数个数越多,产生哈希冲突的概率越小,但是浪费的空间越多。
//假设布隆过滤器中元素类型为K,每个元素对应3个哈希函数,即一个值会映射三个位置
template
class BloomFilter
{
public:
	BloomFilter(size_t size) // 布隆过滤器中元素个数
		: _bmp(10 * size)
		, _size(0)
	{}

private:
	BitMap _bmp;//底层由BitMap实现
};
布隆的插入
  • 思想描述
    【数据结构】—— 哈希的应用之布隆过滤器_第3张图片【数据结构】—— 哈希的应用之布隆过滤器_第4张图片
  • 代码实现
	bool Insert(const K& key)
	{
		size_t bitCount = _bmp.Size();

		size_t index1 = HashFunc1(key) % bitCount;
		size_t index2 = HashFunc2(key) % bitCount;
		size_t index3 = HashFunc3(key) % bitCount;

		_bmp.SetBit(index1);
		_bmp.SetBit(index2);
		_bmp.SetBit(index3);

		++_size;
	}
布隆过滤器的查找
  • 思想描述
    布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中,存在误差。

  • 代码实现

	bool IsBloomFilter(const K& key)
	{
		size_t bitCount = _bmp.Size();

		size_t index1 = HashFunc1(key) % bitCount;
		if (!_bmp.TestBit(index1))
			return false;

		size_t index2 = HashFunc2(key) % bitCount;
		if (!_bmp.TestBit(index2))
			return false;

		size_t index3 = HashFunc3(key) % bitCount;
		if (!_bmp.TestBit(index3))
			return false;

		return true;//可能存在
	}

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

  • 比如:在布隆过滤器中查找abclle"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实际该元素是不存在的。

布隆过滤器优点

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

布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身,只能判断其存不存在
  3. 一般情况下不能从布隆过滤器中删除元素(可以支持删除,但是效率不高且浪费空间)
  4. 如果采用计数方式删除,可能会存在计数回绕问题

布隆过滤器的应用

  • 黑名单
    比如邮件黑名单过滤器,判断邮件地址是否在黑名单中

  • 排序(仅限于BitSet)
    仔细想想,其实BitSet在set(int value)的时候,“顺便”把value也给排序了

  • 网络爬虫
    判断某个URL是否已经被爬取过

  • K-V系统快速判断某个key是否存在
    典型的例子有Hbase,Hbase的每个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存在,如果不存在,直接返回,节省掉后续的查询。

布隆过滤器源码

  • 底层实现的是BitMap,可参考博客:哈希应用之位图
#include "BitMap.h"
#pragma once

#include 
using namespace std;

struct HashFunc1
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (int i = 0; i < s.size(); i++)
		{
			hash = hash * 131 + s[i];
		}
		return hash;
	}
};

struct HashFunc2
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (int i = 0; i < s.size(); i++)
		{
			hash = hash * 65599 + s[i];
		}
		return hash;
	}
};

struct HashFunc3
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (int i = 0; i < s.size(); i++)
		{
			hash = hash * 1313 + s[i];
		}
		return hash;
	}
};
//假设布隆过滤器中元素类型为K,每个元素对应3个哈希函数,即一个值会映射三个位置
template
class BloomFilter
{
public:
	BloomFilter(size_t size) // 布隆过滤器中元素个数
		: _bmp(10 * size)
		, _size(0)
	{}

	bool Insert(const K& key)
	{
		size_t bitCount = _bmp.Size();

		size_t index1 = HashFunc1(key) % bitCount;
		size_t index2 = HashFunc2(key) % bitCount;
		size_t index3 = HashFunc3(key) % bitCount;

		_bmp.SetBit(index1);
		_bmp.SetBit(index2);
		_bmp.SetBit(index3);

		++_size;
	}

	bool IsBloomFilter(const K& key)
	{
		size_t bitCount = _bmp.Size();

		size_t index1 = HashFunc1(key) % bitCount;
		if (!_bmp.TestBit(index1))
			return false;

		size_t index2 = HashFunc2(key) % bitCount;
		if (!_bmp.TestBit(index2))
			return false;

		size_t index3 = HashFunc3(key) % bitCount;
		if (!_bmp.TestBit(index3))
			return false;

		return true;//可能存在
	}

private:
	BitMap _bmp;
	size_t _size;
};

你可能感兴趣的:(数据结构学习代码)