1. 布隆过滤器的使用需求是:查询一个东西是否在一个集合中。例如现在有 100 亿个url被列为黑名单,每次用户访问到该 url 时,返回 false。如果单纯地使用 HashSet,至少 6400 亿字节内存的损耗。而布隆过滤器可以极大程度降低这种内存需求,实现该功能。但布隆过滤器是存在短板的,即失误率。但这种失误率不是被列为黑名单的 url 被判断不在黑名单中, 而是不在黑名单的 url 被判断在了黑名单中。简单来说,就是宁可错杀三千,不放过一个
2. 在面试官问这个问题时,首先讲一下经典解法。然后问面试官,该系统允许较低的失误率吗?如果允许,再说布隆过滤器
3. 首先,介绍一个 bit 数组,如果一个 int[] a = new int [1000],那么这个数组有 32000 个字节数,也就是 1000 大小的 int 数组可以看做是 32000 个大小的 bit 类型数组。如果此时需要将 30000 这个位置 bit 赋值为 1,如何操作?首先用 30000/32,看 30000 这个位置的 bit 在 int 类型数组的哪个位置,之后 30000%32,看 30000 这个 bit 在 int 类型位置的第几个 bit。通过除和模的运算,可以将 30000 这个位置的 bit 赋值为1。注意,1<<16,意思是左移16位,也就是只有第 16 个位置赋值为 1 了,其他还是 0
int[] arr = new int[1000];
int index = 30000;
int intIndex = index / 32;
int bitIndex = index % 32;
arr[intIndex] = (arr[intIndex] | (1 << bitIndex));
4. 布隆过滤器实际操作便是借助 bit 数组实现的。每个 url 通过 k 个哈希函数计算出 k 个哈希值,然后 k 个哈希值都 % m(m为bit数组的大小),然后将模出来的结果对应的 bit 位置设为1。当所有 url 都这样操作后,这个 bit 数组就是黑名单。如果新来的 url 经过 k 个哈希函数运算后,k 个 bit 位置均为 1,那么这个 url 就是在黑名单之中。由此也可以看出,会存在误伤的现象。如果 m 较小,黑名单的 url 将所有的 bit 位置都置为了1,那么每个新来的 url 都会被当做黑名单。直观上,空间 m 越大,失误率就会越低。
5. 在深入分析 m 和 k 之前,需要注意一点,空间 m 的大小与单个 url 的长度无关,只与 url 的总数量有关。无论单个 url 是 64 字节还是 128 字节,经过哈希运算,结果只有 0 和 1。空间 m 的大小实际上是由url 总数量和预期失误率决定的,,其中 n 就是 url 总数量,p 是预期失误率。如果 url 数量 100 亿,p 为 0.0001 时,m 为 131,571,428,572 bit 大小,转为字节约为 16G 大小。和先前的 6400 G相比,所需空间缩小非常多。哈希函数个数 k 的公式为 ,根据所需空间大小和 url 总数量确定,结果向上取整,此处约为 13 个。正因为这样 m 和 k 存在向上取整的情况,所以在选定好 m 和 k 后,实际失误率和预期失误率是有所不同的,此时需要计算实际失误率,公式为 , 此题为十万分之六,和先前万分之一比,降低了一点。三个公式,决定了布隆过滤器的空间大小,哈希函数个数,实际失误率。