1. 問題情景
如果面試官問你,一個網站有 100 億 url 存在一個黑名單中,每條 url 平均 64 位元組。問這個黑名單要怎麼存?若此時隨便輸入一個 url,如何判斷該 url 是否在這個黑名單中?
對於第一個問題,如果把黑名單看成一個集合,將其存在 hashmap 中,貌似太大了,需要 640G,明顯不科學。
那該怎麼辦?ok,現在該介紹今天的主角了 —— 布隆過濾器就可以解決這樣的問題。
首先,布隆過濾器是什麼?維基百科是這樣解釋的:
布隆過濾器(英語:Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進位制向量和一系列隨機對映函式。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。
官方說法看下就好,如果不理解沒關係,其實不會難,下面我們講人話慢慢來。
2. 具體介紹
布隆過濾器實際上是一個很長的二進位制向量和一系列隨機對映函式。
「很長的二進位制向量」:這是一個長度很長的陣列,什麼型別的陣列呢?bit 型別的陣列,也是我們說的「位」,(1Byte = 8bit,1KB = 1024Byte)。
「一系列隨機對映函式」:有多個雜湊函式。那什麼是雜湊函式呢?JDK 裡面有計算得到雜湊值的方法,那就是一個雜湊函式。
布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難
這個不就可以解決我們最開始的問題嗎?那它是怎麼解決的呢?
3. 解決過程
下面我說下大體的過程,細節部分可先不理解,重要的是明白流程,細節我後面補充。
假設,bit 型別陣列的長度為 m,每個元素值為 0,有 k 個雜湊函式。
首先,當輸入一個 url 的時候,此時這個 url 會經過 k 個雜湊函式處理,得到多個雜湊值(v1,v2,...,vk)。之後得到這些雜湊值對應在陣列的下標位置,最後將這些下標的元素都置為 1。
那麼如何判斷一個 url 在黑名單裡面呢?輸入一條 url,它經過上述處理之後,會得到多個陣列的下標位置。如果這些下標的元素值都已經為 1 了,說明該在黑名單裡面,否則不在。
總體就是這樣的流程,下面說下大家可能存在的疑問:
1、bit 型別的陣列如何構建
2、得到 v1,v2,...,vk 這些雜湊值後,如何得到其在陣列的下標位置,並將其設定為 1 呢?
兩個問題我一起說下,Java 裡面沒有 bit 這樣的型別,怎麼構建呢?—— 不難,我們可以使用 int,一個 int 是 32 位。
//建立了一個 100 * 32bit 的陣列
int[] arr = new int[100];
// 代表 bit 陣列 0-31 位的元素
arr[0];
複製程式碼
因此上面再會說「分別將這些雜湊值除以陣列的長度 m,和對 m 取模,得到這些雜湊值對應在陣列的下標位置」。
具體我們可以拿一個雜湊值 data 來舉個栗子,假設 int 陣列長度為 100。
void Set(int data) {
// ByteNO 是表示在 table 陣列中那個元素
int ByteNo = data / 32;
// bitNo 是表示在 32 位 bit 中哪個 bit 位。
int BitNo = data % 32;
// 置 1
_table[ByteNo] |= (1 << BitNo);
}
複製程式碼
4. 使用效果
最開始我們提到,如果將 100 億 url 放到 HashMap 中需要 640GB,那麼使用布隆過濾器後又需要多少空間呢?答案是約等於 23 GB。相比之下,這個空間大小是不是就可以接受很多了。
5. 缺點
布隆過濾器有寧可錯殺一百,也不能放過一個的性質。講人話就是屬於黑名單的 url 一定能夠正確判斷它在黑名單中,但不屬於黑名單中的 url 也可能會被認為在黑名單中,存在一定的失誤率。
至於失誤率要保持在多少,陣列長度,雜湊函式的個數分別要設定多少就需要根據實際情況來選擇了,網上有對應的數學公式計算,這裡就不展開講了。
參考: blog.csdn.net/wenqiang120…
PS:本文原創釋出於微信公眾號「不只Java」,後臺回覆「Java」,送你 13 本 Java 經典電子書。公眾號專注分享 Java 乾貨、讀書筆記、成長思考。