布隆过滤在代码开发中有巨大的使用场景,经典的面试题在一亿个数字中查找是否存在某个值,同时要求内存使用尽可能少,基本都是围绕bitmap和布隆过滤来的。java
业务开发中的布隆过滤使用的场景也比较多,好比作直播app,能够采用布隆过滤实现弹幕敏感词过滤,对于缓存穿透也能够采用布隆过滤。面试
布隆过滤能够迅速判断一个元素是否在一个集合中。redis
咱们使用的场景:算法
网页爬虫对URL的去重,避免爬到相同的URL地址。网页爬虫
反垃圾邮件系统,从十几亿垃圾邮件列表中判断某个邮箱是否存在垃圾邮件(固然能够采用贝叶斯算法)。数组
缓存击穿,将已经存在的缓存方到布隆过滤器里,当黑客刻意的访问不存在的key时能够避免缓存穿透后DB压力过大宕机。缓存
原理能够简单的理解为内部维护一个全为0的bit数组,布隆过滤器存在明显的一个缺点是存在误判几率,数组越长,误判几率越低,可是空间占用越大。服务器
好比咱们生成一个10位的bit数组,及两个hash函数(f1,f2),生成的数组以下: 假设输入的集合为(n1,n2),通过f1(n1)获得的值为2,f2(n2)获得的值为5,则数组下标2和5的位置改成1: 这时若是咱们有第三个数n3,判断n3是否在n1,n2中,能够进行f1(n3),f2(n2)计算,若是获得的值在红色(n1,n2)位置,则认为存在,不然认为不存在。app
这就是布隆过滤的基本原理。函数
引入我最喜欢的guava包:
<dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>22.0</version> </dependency> </dependencies>
代码:
package bloomfilter; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import java.nio.charset.Charset; public class Test { private static int size = 1000000; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size); public static void main(String[] args) { for (int i = 0; i < size; i++) { bloomFilter.put(i); } long startTime = System.nanoTime(); // 获取开始时间 //判断这一百万个数中是否包含29999这个数 if (bloomFilter.mightContain(29999)) { System.out.println("命中了"); } long endTime = System.nanoTime(); // 获取结束时间 System.out.println("程序运行时间: " + (endTime - startTime) + "纳秒"); } }
执行结果:
命中了 程序运行时间: 219386纳秒
对于一个百万级别的集合,只要0.219ms就能够完成。
看看偏差:
package bloomfilter; import java.util.ArrayList; import java.util.List; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class Test { private static int size = 1000000; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size); public static void main(String[] args) { for (int i = 0; i < size; i++) { bloomFilter.put(i); } List<Integer> list = new ArrayList<Integer>(1000); //故意取10000个不在过滤器里的值,看看有多少个会被认为在过滤器里 for (int i = size + 10000; i < size + 20000; i++) { if (bloomFilter.mightContain(i)) { list.add(i); } } System.out.println("误判的数量:" + list.size()); } }
输出结果:
误判对数量:330
误判率为0.03.即,在不作任何设置的状况下,默认的误判率为0.03。
看看源码:
修改bloomfilter的构造方法:
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,0.01);
此时误判率为0.01。在这种状况下,底层维护的bit数组的长度以下图所示:
因而可知,误判率越低,则底层维护的数组越长,占用空间越大。所以,误判率实际取值,根据服务器所可以承受的负载来决定。
缓存穿透控制:
String get(String key) { String value = redis.get(key); if (value == null) { if(!bloomfilter.mightContain(key)){ return null; }else{ value = db.get(key); redis.set(key, value); } } return value; }