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