问题假设
在讲布隆过滤器之前,我们先设想这样的一个问题,目前有100亿个网络黑名单URL,假设一个URL是64字节,设计一种方法,该方法使得用户访问这些网址时,给予警告信息。
注:该方法可以实现加入URL,查询URL,但是没有删除URL的功能
想法一
用HashSet,将所有的这些URL根据哈希函数算出的哈希值得知对应放进HashSet的位置,用户在访问的时候查找哈希表即可。
分析
因为一个URL占有64字节,那么100亿就是6400亿字节,因为10亿字节约等于1G,故需要640G的内存空间。这是十分吓人的。
对应思考
由于所用的内存空间实在是过于巨大,那么有没有其他可以节省空间的做法呢?如果允许一定的失误率的?
想法二
我们观察到,一个4字节的空间就占有32位,一个8字节的空间就占有64位,这是极大的利用空间,如果我们可以基于位运算,就会方便不少。布隆过滤器就是这样,在讲具体的方法之前,我们先回顾一下有关位运算的一些操作。
位运算回顾
public static void main(String[] args) {
//a 是 32bit
int a = 0;
int[] arr = new int[10];//320bit
//arr[0] int 为0 ~ 31位位置
//arr[1] int 为32 ~ 63位位置
//arr[2] int 为64 ~ 95位位置
int i = 178;//i表示想取到的178个bit的状态
int numIndex = 178 / 32;//定位到178位在那一个arr[?]中
int bitIndex = 178 % 32;//定位到在arr[?]的具体那一个位置上
//拿到178位的状态
int s = ((arr[numIndex] >> (bitIndex)) & 1);
//把178位的状态改成1
arr[numIndex] = arr[numIndex] | (1 << (bitIndex));
//把178位的状态改成0
arr[numIndex] = arr[numIndex] & (~(1 << bitIndex));
//整体来讲
i = 178;
int bit = (arr[i / 32] >> (i % 32)) & 1;
}
布隆过滤器讲解
在回顾完位运算之后,那么我们就开始讲解布隆过滤器。布隆过滤器就是一个大的位图。是一个长度为m的bite类型的数组。实际占用m / 8的字节数(内存)。
加入黑名单URL1的步骤
- 将黑名单URL1经过哈希函数f1(x)得到一个哈希值out1,对out1%m得到在具体哪一位上
- 将对应位标黑
- 再将URL1经过哈希函数f2(x)得到一个哈希值out2,对out2%m得到在具体哪一位上
- 将对应位标黑
- …
- …
- 再将URL1经过哈希函数fk(x)得到一个哈希值outk,对outk%m得到在具体哪一位上(一共经历了K个哈希函数)
- 将对应位标黑
- 至此URL1加入完毕
对100亿个黑名单URL都进行上面的步骤,将得到很多位标黑的一个m位位图
查询某个URL是不是黑名单中的URL
- 调用k个哈希函数得到k个哈希值,%m得到k个位置
- 在位图中拿状态,只要有一个位置不是1(标黑),就是白名单,全是1就认为是黑名单中的URL
注:黑名单 --> 白名单 不会发生。但是,白名单 --> 黑名单 可能会发生,原因就是m可能很小,或者k很小或者k很大,都会造成相应的失误率。也就是说,布隆过滤器会有一定的失误率。
参数的确定
上面介绍了参数的选择会影响相应的失误率,那么该怎样确定参数呢?
当n固定,m过小的时候,不同的URL标黑的位置可能会出现重合,造成误判。m变大会使误判变小,但是随着m的增大误判率减小的趋势会越来越缓慢。
当n、m固定,k过小的时候,由于没有足够的采集信息点,所以导致不同的URL标黑的位置可能会出现重合,造成误判。k过大的时候,由于过多的采集信息,相当于m变小了,也会造成误判。
鉴于上面的情况,大神家们总结出了相应的公式:
上面的图片中可以加上一项,就是询问面试官可不可以扩充空间,可以的话,最大是多少。这样得到了新m,将新m和第二个公式算出来的k结合算出最小的失误率。
相关建议
遇到类似黑名单的问题,明显特征是大数据、只加入数据和查询数据,但是不删除数据的类似问题。询问面试官允许有失误率吗?这样一看你就是会的。
到最后根据面试官给出的失误率算出来自己的m大小,问可不可以扩充空间,可以的话,最大是多少。这样得到了新m,将新m和第二个公式算出来的k结合算出最小的失误率。完美解决。