天天看點

JAVA 你應該有所了解的布隆過濾器

布隆過濾器: 我說它不存在,它 百分之兩百 不存在!

(我說它存在,可能我有說錯的時候...)

前言:

該篇文章将會使用最精簡易懂的文字以及小圖來給大家介紹講解(不對哈希政策計算進行詳解)

一.布隆過濾器有啥用?

二.布隆過濾器原理是什麼?

三.java中怎麼使用布隆過濾器?

本篇文章内容可能較多,是以還請耐心。

一. 布隆過濾器  有啥用

簡單兩點叙述:

1. 存值

存值,就是把值存進去, 類似于我們很常用的map,set等;

2.檢驗值是否存在

檢驗,也就是我們想知道某個值是否存在于布隆過濾器裡面,調用相關的檢驗方法,我們會得知一個結果。

特别注意:

布隆過濾器校驗值的結果, 如果傳回結果是 false ,不存在, 那麼是肯定不存在的!

但是如果傳回結果是true,存在,也許有機率是誤報的。

為啥? 看下面的實作原理你就能明白。

原理是什麼

 首先,從上邊介紹的布隆過濾器作用來說,就是個存東西的容器。

但是這個容器有也有自己的特點,

1. 它是一個有固定的大小的,也就是一開始設定它能放多少東西。

2.校驗元素的準确率(指 true 存在的準确率),是會随着存的東西越多而準确率會慢慢降低

怎麼才能更好地了解這個所謂的原理?

我選擇根據布隆過濾器的作用角度去給大家講解。

 首先是存值的原理

既然是可以存放值,然後後面又能校驗存在與否,那麼這個存值顯然不是直接丢進去就好,是需要通過算法計算的。

那麼這裡我不對這個算法進行深入地介紹,但是我會以我的圖文方式告訴你,過程和結果。

模拟場景

一個完全是空的布隆過濾器,是一個由m個二進制位構成的數組,這裡我們假設就是m=5個構成的。

JAVA 你應該有所了解的布隆過濾器

預設一開始,這5個二進制位,簡單了解為位置, 都是綁定着一個辨別 0.

那麼接下來看怎麼存值。

布隆過濾器存值,實際上不是存的 真實值,而且通過算法計算後,存入對應的辨別。

過程簡述,也就是布隆過濾器拿到我們傳入的值, 它會将這個值,使用 K個 哈希函數去進行算法計算,計算完後,

會得出K個 二進制位的辨別 0或者1 ,然後對應位置就會存入這個辨別0或者1.

存值過程舉例說明:

我們需要存入的值為  ‘JCTest’ ,

布隆過濾器會采取相關的 計算公式,來算出 需要使用幾個 哈希函數,也就是K的數量:

(m是數組長度,n是插入的元素個數,k是hash函數的個數)

JAVA 你應該有所了解的布隆過濾器

公式大家可以簡單了解下, 我們現在假設  K 是 3.

也就是說   ‘JCTest’ 需要經過3個哈希函數計算 h1 ,h2,h3。

那麼經過算法計算後, 得出的結果是:

二進制 位置 是  一

二進制 位置 是  三

二進制 位置 是  五

那麼這時候,對應的圖會變成這樣:

一,三,五 位置的辨別此刻會綁定上1

JAVA 你應該有所了解的布隆過濾器

同樣,我們存入第二個值 ‘BFTest’,假設也是需要經過 3個哈希函數,

計算出的二進制位 分别是 二 ,三,五, 那麼此刻的布隆過濾器如:

JAVA 你應該有所了解的布隆過濾器

到這裡,其實我們已經了解了這個布隆過濾器的存值方式, 直白的說就是,每個值對于它來說,都會轉化成幾個位置的 1 ,隻要你往裡面存,它就會把位置變成1 ,直到所有位置都變成1了,那麼就不能再存了(大家不用擔心這個位置會不會很快就都變成1,因為我這裡隻是舉例了5個位置,後面的使用介紹環節會告訴你存上百萬都不是問題,因為這些m值,K值,都會根據我設定的存入個數size和期待誤判率進行算法生成的。)

接下來講解布隆過濾器的校驗值存在與否的原理:

很簡單,其實也是我們告訴布隆過濾器一個值, 它也會通過K計算公式,取出一定數量的哈希函數,也是計算出很多個位置,

然後去一個個位置監測,如果每個位置都是1,這時候 它會傳回true,告訴我們,這個值存在;

而如果隻要存在一個位置是0,這時候,它會傳回 false,告訴我們,這個值不存在。

校驗舉例說明:

如果我們想校驗一個值 ,‘JCTest’,

布隆過濾器通過公式計算得出它準備使用三個函數 h1,h2,h3,

然後計算出對應的二進制位 一,三 ,五  ,接着去檢查對應的位置上綁定的辨別,如果都是1,那麼就會傳回存在 true。

誤判舉例說明:

那怎麼會誤判呢?

那就是例如,我們傳入一個需要校驗的值,‘Test’,布隆過濾器通過相關函數算出,位置恰好也是一,二,三 ,那我們實際上根本沒存過Test,

但是因為我們之前存JCtest 和 BFTest 導緻了相關的位置都綁定了辨別1,是以這時候布隆過濾器就産生了誤判現象。(這個機率不高其實,接下來的實用介紹會了解到)

PS: 也是因為這種校驗的政策,是以隻要在校驗的時候,檢測到一個對應的位置不為1,那麼毫無疑問!這個值是百分之兩百不存在的!

怎麼使用布隆過濾器

了解了布隆過濾器的作用和原理後,那麼我們接下來在java中使用它。

該篇就不去手動實作布隆過濾器了,因為 在GoogleGuavalibrary中Google為我們提供了一個布隆過濾器的實作。

是以我們第一步,導入jar包:

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>      

然後建一個BloomFilterTest.class :

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * @Author : JCccc
 * @CreateTime : 2020/3/31
 * @Description :
 **/
public class BloomFilterTest {


    private static int size = 1000000;//預計要插入多少資料

    private static double fpp = 0.01;//期望的誤判率


    public static void main(String[] args) {


        //存入的是字元串
        BloomFilter<CharSequence> bloomFilterStr = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), size, fpp);

        for (int i = 0; i < size; i++) {
            bloomFilterStr.put("test" + i);
        }
        boolean containsValue1 = bloomFilterStr.mightContain("test1");
        boolean containsValue2 = bloomFilterStr.mightContain("test34");
        boolean containsValue3 = bloomFilterStr.mightContain("test1000001");
        System.out.println(containsValue1);
        System.out.println(containsValue2);
        System.out.println(containsValue3);


    }
}      

校驗結果:

JAVA 你應該有所了解的布隆過濾器

然後再試下,使用布隆過濾器存入數字:

BloomFilter<Integer> bloomFilterInt = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

        for (int i=0; i<size;i++){
            bloomFilterInt.put(i);
        }
        boolean containsValue4 = bloomFilterInt.mightContain(1);
        boolean containsValue5 = bloomFilterInt.mightContain(2);
        boolean containsValue6 = bloomFilterInt.mightContain(10000002);
        System.out.println(containsValue4);
        System.out.println(containsValue5);
        System.out.println(containsValue6);      

結果:

JAVA 你應該有所了解的布隆過濾器

可以看到以上兩存值&校驗的測試都是準确無誤的,那麼我們大規模的存值檢查,看看存在的誤判現象:

 我們剛剛往 bloomFilterInt 這個過濾器裡面存入了0到1000000 個數字值,

那麼接下來我們直接從1000000開始去累加到2000000,去進行校驗,

我們明确知道 這時候的布隆過濾器裡面有1百萬個值,而我們的1000000到2000000除了‘1000000’其實都是不存在的,都應該傳回false。

接下來我們用代碼來模拟下這個判斷存在 的誤判現象:

int size = 1000000;//預計要插入多少資料
        double fpp = 0.01;//期望的誤判率
        BloomFilter<Integer> bloomFilterInt = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

        for (int i=0; i<size;i++){
            bloomFilterInt.put(i);
        }
        
        int count = 0;
        for (int i = 1000000; i < 2000000; i++) {
            if (bloomFilterInt.mightContain(i)) {
                count++;
                System.out.println(i + "誤判了");
            }
        }
        System.out.println("總共的誤判數:" + count);      

結果:

JAVA 你應該有所了解的布隆過濾器

在這一百萬個數值的布隆過濾器裡面,再校驗一百萬次,存在的誤判數是10314次。 

是以咱們使用布隆過濾器進行值的存入和校驗時,

我們應該更偏向使用 它傳回的false 進行結合業務做操作,因為它的判斷如果傳回fasle,那是百分之兩百可信的!

當然其實true誤報的情況,機率也是非常小的,在一定的業務場景也能使用的其實。

ps:

例如,我們先到布隆過濾器查詢一個訂單是否存在,不存在就當失敗訂單處理;

存在的話,我們再去資料庫擷取詳情資訊進行訂單的後續操作。

這時候隻需要我們再接收到訂單存入資料庫的時候,把單獨的訂單号存入布隆過濾器即可。

就算布隆過濾器誤判了,我們再去資料庫查詢訂單時,加上多一步校驗存在與否即可。

這樣就可以減輕每個訂單都需要查資料庫的壓力。

(其實有過了解redis使用的小夥伴,就明顯知道布隆過濾器配和redis使用會非常不錯,可以一定程度防止緩存穿透,這些我有時間會在我的redis欄目寫一篇對緩存穿透和緩存雪崩的介紹和解決思路相關的文章)

那麼這篇普及布隆過濾器就先到這吧。