有100億個url被加入了黑名單,現在提供一個url要去判斷是否屬於黑名單。也就是一個很簡單的一個東西是否屬於一個集合的問題。
一般來說用set就能解決這種問題,但是由於url數目太多,記憶體中無法開闢一個這麼大的空間去存放所有url,這個時候就需要我們去使用一種結構,去減少狀態資訊儲存所需要的記憶體,而布隆過濾器就可以很好地實現這個功能。
在瞭解布隆過濾器演演算法前,需要先了解一些前置知識,例如雜湊函數和點陣圖
雜湊函數就是一個對映函數,可以把任意長的輸入位(或位元組)變化成固定長的輸出字串的一種函數。理想的雜湊函數是從所有可能的輸入值得到所可能的有限輸出值的一個隨機對映。
性質
總而言之,雜湊函數就是一個對映函數,計算的結果H(m)會在值域上離散均勻分佈
點陣圖,bitmap或者bitarray,就是用bit位去儲存資訊的一種結構。正常的陣列要麼是int陣列或者是long陣列,每個元素是4位元組或者8位元組,也就是32bit或者64bit的資訊表示一個元素或者一種狀態。而bitarray就是1個bit表示陣列裡的一個元素或者一種狀態,這樣的優點就是非常省空間,本來用幾十個bit表達的資訊被它用1個bit就表達了(前提是資訊能用點陣圖表達的情況下)。
下面一串數位0和1即是點陣圖的表示,一共有32個數位,分別表示陣列裡的32個元素,這些元素的值要麼是0要麼是1。
因為點陣圖的每一個元素只有2個值,一般可以用來判斷一個數是否出現過這種簡單的布林值是或否的問題。
如何實現一個點陣圖?因為我們的數位都是32或64bit的,所以需要藉助真實的數位來實現bitmap。現在設一個數位是32個bit,現在有一個長度位10的陣列,這個陣列可以用來表示長度為320的bit型別的陣列,然後通過位運算去實現從bit陣列裡面取值
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let i = 199 //想要獲取bit陣列裡第199個元素的值
let numIndex = Math.floor(i / 32) //找到arr裡的對應的元素
let bitIndex = i % 32 //找到對應元素裡的位數
let value = (arr[numIndex] >> bitIndex) & 1 //獲取第199位的值
對bitarray進行賦值操作
arr[numIndex] = arr[numIndex] | (1 << bitIndex) //將第199位變成1
arr[numIndex] = arr[numIndex] & (~(1 << bitIndex)) //將199位變成0
實現BitMap類
程式碼、案例如下
class BitMap { constructor(size) { this.size = Math.ceil(size / 32) this.bitArray = new Array(this.size).fill(0) } getNumber(num) { //獲取位數 let numIndex = Math.floor(num / 32) let bitIndex = num % 32 return (this.bitArray[numIndex] >> bitIndex) & 1 } isExist(num) { //判斷一個數是否存在 let value = this.getNumber(num) return value == 1 } addNumber(num) { //新增一個數 let numIndex = Math.floor(num / 32) let bitIndex = num % 32 this.bitArray[numIndex] = this.bitArray[numIndex] | (1 << bitIndex) } }
舉例,現在有一個arr陣列,arr陣列裡每個元素都是0-100的數,給定一個值x判斷x是否出現過。
對於這種值範圍是給定的情況下,相比起普通的建立map或set統計是否出現過,這個做法只需要4個元素的額外陣列空間,大大節省了空間
回到前面的問題,對於100億個url的黑名單,現在希望也可以通過點陣圖的方式來完成判斷一個url是否出現在黑名單內,這樣可以極大地壓縮空間,但是url這種字串是無法直接使用點陣圖的,所以需要藉助雜湊函數的對映。
此時已經把url變成0-m的數位了,已經可以直接用點陣圖去判斷是否存在了,但是仍然有一個問題,就是雜湊碰撞。不同的輸入也有可能會有相同的輸出,如果m不夠大,很可能存在一個不在黑名單的url在經過雜湊函數的計算和取模之後,結果與黑名單中url的計算結果相同,那麼不在黑名單的url就會被誤判誤封。
解決這個問題的辦法可以是
而m和k的取值則要取決於樣本量n和允許的失誤率p
m自然是樣本越多,允許的失誤率越低,取得值越大,當然記憶體足夠的情況下也可以取得比公式裡更多,這樣可以降低失誤率
k的取值取決於m和n的比例,如果k取得太少,那麼和之前的1個雜湊函數計算差別不大,如果k取得太大,一次計算下來很多位置都設定成了1,再想判斷的時候就很可能失誤
設定好了m和k,就可以按照之前的點陣圖演演算法來進行判斷一個url是否是黑名單內的url了