Bloom Filter:海量数据的HashSet

Bloom Filter一般用于数据的去重计算,近似于HashSet的功能;但是不同于Bitmap(用于精确计算),其为一种估算的数据结构,存在误判(false positive)的情况。

1. 基本原理

Bloom Filter能高效地表征数据集合(S = lbrace x_1 ,x_2 ,...,x_n brace),判断某个数据是否属于这个集合。其基本思想如下:用长度为(m)的位数组(A)来存储集合信息,同时是有(k)个独立的hash函数(h_i(1le i le k))将数据映射到位数组空间。具体流程如下:

  1. 将长度为(m)的位数组全置为0;
  2. 对于数据(x in S),依次计算其(k)个hash函数值(h_i(x)=w,且1le i le k, 1 le w le m),将位数组中的第(a)位bit置为1,即A[w]=1.

当查询数据(y)是否属于集合(S)时,计算其(k)个hash函数值,如果(h_i(y))对应的位数组均为1,则数据(y)属于集合(S);反之,则不属于。

2. 相关计算

在上述判断中,可能存在误判(false positive, FP),比如某数的(k)个hash函数值可能属于集合(S)中某几个数(k)个hash函数值组成的集合。显然,误判率跟集合大小(n)、位数组大小(m)、hash函数的个数(k)有关;在其他条件不变的情况下,若(n)越大((m)越小,或(k)越多),则误判率越高。误判率估算公式如下:

[P_{fp} approx (1-e^{-kn/m})^k ]

在实际的场景中,常常是已知集合大小(n),预设误判率(P_{fp}),需要计算位数组大小(m)、hash函数的个数(k)。通过一系列的数学推导,可得到如下公式:

[m= - frac{nln P_{fp}}{(ln 2)^2} ]

[k=frac{m}{n}ln 2 ]

详细的数学推导可参看相关文档。

3. 实战

Bloom Filter的Java实现有Guava、stream-lib,Scala实现有breezebloom-filter-scala。采用breeze库的Distinct Count实现如下:

import breeze.util.BloomFilter

val bf = BloomFilter.optimallySized[Int](5, 0.01)
val arr = Array(1, 3, 4, 5, 1, 2, 6, 3, 1)
var cnt = 0
arr.foreach { t =>
  bf.contains(t) match {
    case false => cnt += 1; bf.+=(t)
    case _ =>
  }
}
println(arr.distinct.length) // 6
println(cnt) // 6

从上面的Scala代码中,不难发现:在Distinct Count计算过程中,需要定义一个global变量,逐一用于对每个不属于集合元素进行计算。显然,在分布式计算中,这种方法不太适用;因为global变量没法做到实时的传递更新。因此,另一种估算算法HyperLogLog,拥有优秀的可加性、易于并行化,在大数据的场景下应用广泛——Spark、Kylin中的近似Distinct Count便是基于此。

4. 参考资料

[1] Broder, Andrei, and Michael Mitzenmacher. "Network Applications of Bloom Filters: A Survey." Internet Mathematics 1.4 (2011): 485-509.
[2] 张俊林, 《大数据日知录》.

原文地址:https://www.cnblogs.com/en-heng/p/5881997.html