前言
在日常工作中,经常要判断一个元素是否在一个集合中。假设你要向浏览器添加一项功能,该功能可以通知用户输入的网址是否是恶意网址,此时你手上有大约 1000 万个恶意 URL 的数据集,你该如何实现该功能。按我之前的思维,要判断一个元素在不在当前的数据集中,首先想到的就是使用 hash table,通过哈希函数运行所有的恶意网址以获取其哈希值,然后创建出一个哈希表(数组)。这个方案有个明显的缺点,就是需要存储原始元素本身,内存占用大,而我们其实主要是关注 当前输入的网址在不在我们的恶意 URL 数据集中,也就是之前的恶意 URL 数据集的具体值是什么并不重要,通过吴军老师的《数学之美》了解到,对于这种场景大数据领域有个用于在海量数据情况下判断某个元素是否已经存在的算法很适合,关键的一点是该算法并不存储元素本身,这个算法就是 — 布隆过滤器(Bloom filter)。
原理
布隆过滤器是由巴顿.布隆于一九七零年提出的,在 维基百科 中的描述如下:
A Bloom filter is a space-efficient probabilistic data structure, conceived by Burton Howard Bloom in 1970, that is used to test whether an element is a member of a set.
布隆过滤器是一个数据结构,它可以用来判断某个元素是否在集合内,具有运行快速,内存占用小的特点,它由一个很长的二进制向量和一系列随机映射函数组成。而高效插入和查询的代价就是,它是一个基于概率的数据结构,只能告诉我们一个元素绝对不在集合内,布隆过滤器的好处在于快速,省空间,但是有一定的误判率。布隆过滤器的基础数据结构是一个比特向量,假设有一个长度为 16 的比特向量,下面我们通过一个简单的示例来看看其工作原理:
上图比特向量中的每一个空格表示一个比特, 空格下面的数字表示当前位置的索引。只需要简单的对输入进行多次哈希操作,并把对应于其结果的比特置为 1,就完成了向 Bloom filter 添加一个元素的操作。下图表示向布隆过滤器中添加元素 https://www.mghio.cn 和 https://www.abc.com 的过程,它使用了 func1 和 func2 两个简单的哈希函数。
当我们往集合里添加一个元素的时候, 可以检查该元素在应用对应哈希函数后的哈希值对比特向量的长度取余后的位置是否为 1,图中用 1 表示最新添加的元素对应位置。然后当我们要判断添加元素是否存在集合中的话,只需要简单的通过对该元素应用同样的哈希函数,然后看比特向量里对应的位置是否为 1 的方式来判断一个元素是否在集合里。如果不是,则该元素一定不再集合中,但是需要注意的是,如果是,你只知道元素可能在里面, 因为这些对应位置有可能恰巧是由其它元素或者其它元素的组合所引起的。以上就是布隆过滤器的实现原理。
如何自己实现
布隆过滤器的思想比较简单,首先在构造方法中初始化了一个指定长度的 int 数组,在添加元素的时候通过哈希函数 func1 和 func2 计算出对应的哈希值,对数组长度取余后将对应位置置为 1,判断元素是否存在于集合中时,同样也是对元素用同样的哈希函数进行两次计算,取到对应位置的哈希值,只要存在位置的值为 0,则认为元素不存在。下面使用 Java 语言实现了上面示例中简单版的布隆过滤器:
public class BloomFilter {
/**
* 数组长度
*/
private int size;
/**