布隆過濾

前言

布隆過濾在代碼開發中有巨大的使用場景,經典的面試題在一億個數字中查找是否存在某個值,同時要求內存使用盡可能少,基本都是圍繞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;
}
相關文章
相關標籤/搜索