Google Guava 中布隆過濾器的介紹和使用

1、簡介

布隆過濾器(Bloom Filter)是很是經典的,以空間換時間的算法。布隆過濾器由布隆在 1970 年提出。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。java

2、實現原理

布隆過濾器的核心實現是一個超大的位數組和幾個哈希函數。假設位數組的長度爲 m,哈希函數的個數爲 k。git

以上圖爲例,具體的操做流程:假設集合裏面有 3 個元素 {x, y, z},哈希函數的個數爲 3。首先將位數組進行初始化,將裏面每一個位都設置位 0。對於集合裏面的每個元素,將元素依次經過 3 個哈希函數進行映射,每次映射都會產生一個哈希值,這個值對應位數組上面的一個點,而後將位數組對應的位置標記爲 1。查詢 W 元素是否存在集合中的時候,一樣的方法將 W 經過哈希映射到位數組上的 3 個點。若是 3 個點的其中有一個點不爲 1,則能夠判斷該元素必定不存在集合中。反之,若是 3 個點都爲 1,則該元素可能存在集合中。注意:此處不能判斷該元素是否必定存在集合中,可能存在必定的誤判率。能夠從圖中能夠看到:假設某個元素經過映射對應下標爲 四、五、6 這 3 個點。雖然這 3 個點都爲 1,可是很明顯這 3 個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明元素雖然不在集合中,也可能對應的都是 1,這是誤判率存在的緣由。算法

一、布隆過濾器添加元素

  • 將要添加的元素給 k 個哈希函數
  • 獲得對應於位數組上的 k 個位置
  • 將這 k 個位置設爲 1

二、布隆過濾器查詢元素

  • 將要查詢的元素給 k 個哈希函數
  • 獲得對應於位數組上的 k 個位置
  • 若是 k 個位置有一個爲 0,則確定不在集合中
  • 若是 k 個位置所有爲 1,則可能在集合中

三、布隆過濾器的優缺點

優勢數據庫

相比於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優點。布隆過濾器存儲空間和插入/查詢時間都是常數。另外,Hash 函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不須要存儲元素自己,在某些對保密要求很是嚴格的場合有優點。
布隆過濾器能夠表示全集,其它任何數據結構都不能。
缺點
可是布隆過濾器的缺點和優勢同樣明顯。誤算率(False Positive)是其中之一。隨着存入的元素數量增長,誤算率隨之增長(誤判補救方法是:再創建一個小的白名單,存儲那些可能被誤判的信息)。可是若是元素數量太少,則使用散列表足矣。
另外,通常狀況下不能從布隆過濾器中刪除元素。咱們很容易想到把位列陣變成整數數組,每插入一個元素相應的計數器加 1, 這樣刪除元素時將計數器減掉就能夠了。然而要保證安全的刪除元素並不是如此簡單。首先咱們必須保證刪除的元素的確在布隆過濾器裏面. 這一點單憑這個過濾器是沒法保證的。另外計數器迴繞也會形成問題。網頁爬蟲

3、使用 Guava 的布隆過濾器

一、添加依賴

Maven:數組

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

Gradle:緩存

// https://mvnrepository.com/artifact/com.google.guava/guava
compile group: 'com.google.guava', name: 'guava', version: '22'

二、使用方法

建立 BloomFilter 對象,須要傳入 Funnel 對象,預估的元素個數,誤判率。安全

BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000,0.01);

使用 put 方法添加元素:網絡

filter.put("test");

使用 mightContain 方法判斷元素是否存在:數據結構

filter.mightContain("test");

三、例子

這個例子中我向布隆過濾器中添加了 10000000 條數據,將 fpp(誤判率)設置爲 0.001(0.1%)。

public class BloomFilterTest {

    private static int insertions = 10000000;

    public static void main(String[] args) {

        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), insertions, 0.001);

        Set<String> sets = new HashSet<String>(insertions);

        List<String> lists = new ArrayList<String>(insertions);

        for (int i = 0; i < insertions; i++) {
            String uid = UUID.randomUUID().toString();
            bloomFilter.put(uid);
            sets.add(uid);
            lists.add(uid);
        }

        int right = 0;
        int wrong = 0;

        for (int i = 0; i < 10000; i++) {
            String data = i % 100 == 0 ? lists.get(i / 100) : UUID.randomUUID().toString();
            if (bloomFilter.mightContain(data)) {
                if (sets.contains(data)) {
                    right++;
                    continue;
                }
                wrong++;
            }
        }

        NumberFormat percentFormat = NumberFormat.getPercentInstance();
        percentFormat.setMaximumFractionDigits(2);
        float percent = (float) wrong / 9900;
        float bingo = (float) (9900 - wrong) / 9900;

        System.out.println("在 " + insertions + " 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:" + right);
        System.out.println("在 " + insertions + " 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:" + wrong);
        System.out.println("命中率爲:" + percentFormat.format(bingo) + ",誤判率爲:" + percentFormat.format(percent));
    }

}

通過我屢次測試,執行結果中的誤判率基本保持在 0.1% 左右,偏差不會太大。

在 10000000 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:100
在 10000000 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:8
命中率爲:99.92%,誤判率爲:0.08%

在 10000000 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:100
在 10000000 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:9
命中率爲:99.91%,誤判率爲:0.09%

在 10000000 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:100
在 10000000 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:10
命中率爲:99.9%,誤判率爲:0.1%

在 10000000 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:100
在 10000000 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:15
命中率爲:99.85%,誤判率爲:0.15%

在 10000000 條數據中,判斷 100 實際存在的元素,布隆過濾器認爲存在的數量爲:100
在 10000000 條數據中,判斷 9900 實際不存在的元素,布隆過濾器誤認爲存在的數量爲:10
命中率爲:99.9%,誤判率爲:0.1%

4、應用場景

利用布隆過濾器減小磁盤 IO 或者網絡請求,由於一旦一個值一定不存在的話,咱們能夠不用進行後續昂貴的查詢請求,好比如下場景:

一、大數據去重;

二、網頁爬蟲對 URL 的去重,避免爬取相同的 URL 地址;

三、反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱;

四、緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及數據庫掛掉。

相關文章
相關標籤/搜索