一道騰訊面試題:如何快速判斷某 URL 是否在 20 億的網址 URL 集合中?布隆過濾器

  • 何爲布隆過濾器算法

    • 仍是以上面的例子爲例:數組

    • 判斷邏輯:緩存

    • 屢次哈希:框架

  • Guava的BloomFilter函數

  • 建立BloomFilter網站

    • 最終仍是調用:url

    • 使用:spa

  • 算法特色code

  • 使用場景內存


假設遇到這樣一個問題:一個網站有 20 億 url 存在一個黑名單中,這個黑名單要怎麼存?若此時隨便輸入一個 url,你如何快速判斷該 url 是否在這個黑名單中?而且需在給定內存空間(好比:500M)內快速判斷出

可能不少人首先想到的會是使用 HashSet,由於 HashSet基於 HashMap,理論上時間複雜度爲:O(1)。達到了快速的目的,可是空間複雜度呢?URL字符串經過Hash獲得一個Integer的值,Integer佔4個字節,那20億個URL理論上須要:20億*4/1024/1024/1024=7.45G的內存,不知足空間複雜度的要求。

這裏就引出本文要介紹的「布隆過濾器」。

何爲布隆過濾器

百科上對布隆過濾器的介紹是這樣的:

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

是否是描述的比較抽象?那就直接瞭解其原理吧!

仍是以上面的例子爲例:

哈希算法得出的Integer的哈希值最大爲:Integer.MAX_VALUE=2147483647,意思就是任何一個URL的哈希都會在0~2147483647之間。

那麼能夠定義一個2147483647長度的byte數組,用來存儲集合全部可能的值。爲了存儲這個byte數組,系統只須要:2147483647/8/1024/1024=256M

好比:某個URL(X)的哈希是2,那麼落到這個byte數組在第二位上就是1,這個byte數組將是:000….00000010,重複的,將這20億個數所有哈希並落到byte數組中。

判斷邏輯:

若是byte數組上的第二位是1,那麼這個URL(X)可能存在。爲何是可能?由於有可能其它URL因哈希碰撞哈希出來的也是2,這就是誤判。

可是若是這個byte數組上的第二位是0,那麼這個URL(X)就必定不存在集合中。

屢次哈希:

爲了減小因哈希碰撞致使的誤判機率,能夠對這個URL(X)用不一樣的哈希算法進行N次哈希,得出N個哈希值,落到這個byte數組上,若是這N個位置沒有都爲1,那麼這個URL(X)就必定不存在集合中。

Guava的BloomFilter

Guava框架提供了布隆過濾器的具體實現:BloomFilter,使得開發不用再本身寫一套算法的實現。

建立BloomFilter

BloomFilter提供了幾個重載的靜態 create方法來建立實例:

public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions, double fpp);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions);
public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions);

最終仍是調用:

static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy);
// 參數含義:
// funnel 指定布隆過濾器中存的是什麼類型的數據,有:IntegerFunnel,LongFunnel,StringCharsetFunnel。
// expectedInsertions 預期須要存儲的數據量
// fpp 誤判率,默認是0.03。

BloomFilter裏byte數組的空間大小由 expectedInsertions, fpp參數決定,見方法:

static long optimalNumOfBits(long n, double p) {
    if (p == 0) {
        p = Double.MIN_VALUE;
    }
    return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}

真正的byte數組維護在類:BitArray中。

使用:

最後經過:put和 mightContain方法,添加元素和判斷元素是否存在。

算法特色

一、因使用哈希判斷,時間效率很高。空間效率也是其一大優點。二、有誤判的可能,需針對具體場景使用。三、由於沒法分辨哈希碰撞,因此不是很好作刪除操做。

使用場景

一、黑名單 二、URL去重 三、單詞拼寫檢查 四、Key-Value緩存系統的Key校驗 五、ID校驗,好比訂單系統查詢某個訂單ID是否存在,若是不存在就直接返回。

相關文章
相關標籤/搜索