布隆过滤

前言

布隆过滤在代码开发中有巨大的使用场景,经典的面试题在一亿个数字中查找是否存在某个值,同时要求内存使用尽可能少,基本都是围绕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;
}