拜託,面試官別問我「布隆」了(補充修訂版)

前言

在以前的 拜託,面試官別問我「布隆」了 一文中,不少小夥伴留言說並不能看出布隆過濾器有比位圖更方便,今天的文章就補充更詳細一點。面試

題目描述

一個網站有 100 億 url 存在一個黑名單中,每條 url 平均 64 字節。這個黑名單要怎麼存?若此時隨便輸入一個 url,你如何快速判斷該 url 是否在這個黑名單中算法

題目解析

這是一道常常在面試中出現的算法題。憑藉着題目極其容易描述,電面的時候也出現過。數據庫

不考慮細節的話,此題就是一個簡單的查找問題。對於查找問題而言,使用散列表來處理每每是一種效率比較高的方案。網頁爬蟲

可是,若是你在面試中回答使用散列表,接下來面試官確定會問你:而後呢?若是你不能回答個因此然,面試官就會面無表情的通知你:今天的面試到此結束,咱們會在一週內給你答覆。數組

爲何不能用散列表

100 億是一個很大的數量級,這裏每條 url 平均 64 字節,所有存儲的話須要 640G 的內存空間。又由於使用了散列表這種數據結構,而散列表是會出現散列衝突的。爲了讓散列表維持較小的裝載因子,避免出現過多的散列衝突,須要使用鏈表法來處理,這裏就要存儲鏈表指針。所以最後的內存空間可能超過 1000G 了。緩存

只是存儲個 url 就須要 1000G 的空間,老闆確定不能忍!bash

位圖(BitMap)

這個時候就須要拓展一下思路。首先,先來考慮一個相似但更簡單的問題:如今有一個很是龐大的數據,好比有 1 千萬個整數,而且整數的範圍在 1 到 1 億之間。那麼如何快速查找某個整數是否在這 1 千萬個整數中呢?服務器

須要判斷該數是否存在,也就是說這個數存在兩種狀態:存在( True )或者不存在(False)。數據結構

所以這裏可使用一個存儲了狀態的數組來處理。這個數組特色是大小爲 1 億,而且數據類型爲布爾類型( True 或者 False )。而後將這 1 千萬個整數做爲數組下標,將對應的數組值設置成 True,好比,整數 233 對應下標爲 233 的數組值設置爲 True,也就是 array[ 233 ] = True。函數

這種操做就是位圖法:就是用每一位來存放某種狀態,適用於大規模數據,但數據狀態又不是不少的狀況。

另外,位圖法有一個優點就是空間不隨集合內元素個數的增長而增長。它的存儲空間計算方式是找到全部元素裏面最大的元素(假設爲 N ),所以所佔空間爲:

計算公式

所以,當 N 爲 1 億的時候須要 12MB 的存儲空間。當 N 爲 10 億的時候須要 120MB 的存儲空間了。當 N 的數量大到必定量級的時候,好比 N 爲 2^64 這個海量級別的時候,須要消耗 2048PB 的存儲空間,這個量級的BitMap,目前硬件上是支持不了的。

也就是說:位圖法的所佔空間隨集合內最大元素的增大而增大。這就會帶來一個問題,若是查找的元素數量少但其中某個元素的值很大,好比數字範圍是 1 到 1000 億,那消耗的空間不容樂觀。

這個就是位圖的一個不容忽視的缺點空間複雜度隨集合內最大元素增大而線性增大。對於開頭的題目而言,使用位圖進行處理,實際上內存消耗也是很多的。

所以,出於性能和內存佔用的考慮,在這裏使用布隆過濾器纔是最好的解決方案:布隆過濾器是對位圖的一種改進。

布隆過濾器

布隆過濾器(英語:Bloom Filter)是 1970 年由 Burton Bloom 提出的。

它其實是一個很長的二進制矢量和一系列隨機映射函數。
複製代碼

能夠用來判斷一個元素是否在一個集合中。它的優點是隻須要佔用很小的內存空間以及有着高效的查詢效率。

對於布隆過濾器而言,它的本質是一個位數組:位數組就是數組的每一個元素都只佔用 1 bit ,而且每一個元素只能是 0 或者 1。

一開始,布隆過濾器的位數組全部位都初始化爲 0。好比,數組長度爲 m ,那麼將長度爲 m 個位數組的全部的位都初始化爲 0。

0 0 0 0 0 0 0 0 0 0
0 0 1 m-2 m-1

在數組中的每一位都是二進制位。

布隆過濾器除了一個位數組,還有 K 個哈希函數。當一個元素加入布隆過濾器中的時候,會進行以下操做:

  • 使用 K 個哈希函數對元素值進行 K 次計算,獲得 K 個哈希值。
  • 根據獲得的哈希值,在位數組中把對應下標的值置爲 1。

圖 1

舉個例子,假設布隆過濾器有 3 個哈希函數:f1, f2, f3 和一個位數組 arr。如今要把 2333 插入布隆過濾器中:

  • 對值進行三次哈希計算,獲得三個值 n1, n2, n3。
  • 把位數組中三個元素 arr[n1], arr[n2], arr[3] 都置爲 1。

當要判斷一個值是否在布隆過濾器中,對元素進行三次哈希計算,獲得值以後判斷位數組中的每一個元素是否都爲 1,若是值都爲 1,那麼說明這個值在布隆過濾器中,若是存在一個值不爲 1,說明該元素不在布隆過濾器中。

布隆

很明顯,數組的容量即便再大,也是有限的。那麼隨着元素的增長,插入的元素就會越多,位數組中被置爲 1 的位置所以也越多,這就會形成一種狀況:當一個不在布隆過濾器中的元素,通過一樣規則的哈希計算以後,獲得的值在位數組中查詢,有可能這些位置由於以前其它元素的操做先被置爲 1 了

如圖 1 所示,假設某個元素經過映射對應下標爲4,5,6這3個點。雖然這 3 個點都爲 1 ,可是很明顯這 3 個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明這個元素雖然不在集合中,也可能對應的都是 1,這是誤判率存在的緣由。

因此,有可能一個不存在布隆過濾器中的會被誤判成在布隆過濾器中。

這就是布隆過濾器的一個缺陷:存在誤判。

可是,若是布隆過濾器判斷某個元素不在布隆過濾器中,那麼這個值就必定不在布隆過濾器中。總結就是:

  • 布隆過濾器說某個元素在,可能會被誤判
  • 布隆過濾器說某個元素不在,那麼必定不在

用英文說就是:False is always false. True is maybe true。

誤判率

布隆過濾器能夠插入元素,但不能夠刪除已有元素。其中的元素越多,false positive rate(誤報率)越大,可是false negative (漏報)是不可能的。因爲公衆號內對於數學公式的排版不太友好,小吳就不在這貼出來了,具體的計算公式能夠在網上查找到。

補救方法

布隆過濾器存在必定的誤識別率。常見的補救辦法是在創建白名單,存儲那些可能被誤判的元素。 好比你苦等的offer 可能被系統丟在郵件垃圾箱(白名單)了。

使用場景

布隆過濾器的最大的用處就是,可以迅速判斷一個元素是否在一個集合中。所以它有以下三個使用場景:

  • 網頁爬蟲對 URL 的去重,避免爬取相同的 URL 地址
  • 進行垃圾郵件過濾:反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信)
  • 有的黑客爲了讓服務宕機,他們會構建大量不存在於緩存中的 key 向服務器發起請求,在數據量足夠大的狀況下,頻繁的數據庫查詢可能致使 DB 掛掉。布隆過濾器很好的解決了緩存擊穿的問題。

回到問題

回到一開始的問題,若是面試官問你如何在海量數據中快速判斷該 url 是否在黑名單中時,你應該回答使用布隆過濾器進行處理,而後說明一下爲何不使用 hash 和 bitmap,以及布隆過濾器的基本原理,最後你再談談它的使用場景那就更好了。

相關文章
相關標籤/搜索