在我的記憶中布穀鳥過濾器一直是說比bloom好,那麼我部落格便以一個diss布穀鳥過濾器的角度來探究
學前須知:本篇立足於讀者瞭解bloomfilter底層實現上
布穀鳥相較於bloom的優點
支援刪除操作
如何支援呢?因為bloom的話是不能支援的,他的一個bit可能代表了多個key存在的情況,所以這個bit位是不能隨便亂動的!
那麼布穀鳥呢?下面這段便是布穀鳥過濾器的原理啦
布穀鳥交配後,雌性布穀鳥就準備產蛋了,但它卻不會自己築巢。它會來到像知更鳥、刺嘴鶯等那些比它小的鳥類的巢中,移走原來的那窩蛋中的一個,用自己的蛋來取而代之。相對於它的體形來說,它的蛋是偏小的,而且蛋上的斑紋同它混入的其他鳥的蛋也非常相似,所以不易被分辨出來。如果不是這樣,它的蛋肯定會被扔出去。
更具體地說是
最原始的布穀鳥雜湊方法是使用兩個雜湊函式對一個key
進行雜湊,得到桶中的兩個位置,此時
- 如果兩個位置都為為空則將
key
隨機存入其中一個位置 - 如果只有一個位置為空則存入為空的位置
- 如果都不為空,則隨機踢出一個元素,踢出的元素再重新計算雜湊找到相應的位置
當然假如存在絕對的空間不足,那老是踢出也不是辦法,所以一般會設定一個踢出閾值,如果在某次插入行為過程中連續踢出超過閾值,則進行擴容。
更現代化的布穀鳥過濾器:
由兩個或者多個雜湊函式構成,布穀鳥過濾器的布穀鳥雜湊表的基本單位稱為條目(entry,說起條目感覺很陌生,其實就是java中map的一個單位)。 每個條目儲存一個指紋(fingerprint)(指紋這個概念大家應該會比較陌生,下文會給出更具體的描述),指紋指的是使用一個雜湊函式生成的n位位元位,n的具體大小由所能接受的誤判率來設定,例如使用8bits的指紋大小。
插入
以最簡單的兩個hash函式為例來進行說明
首先插入值為x,
- 我們需要先計算出指紋
$$
f=fingerprint(x)
$$
- 計算出兩個候選位置
使用兩個不同的雜湊函式 h1
和 h2
計算兩個候選儲存位置 i1
和 i2
。注意這裡計算 i2
時會用到指紋 f
。
$$
i1=h1(x)
$$
$$
i2=h2(f)=i1⊕h(f)
$$
其中,h(f)
是對指紋 f
進行雜湊運算得到的值。
嘗試插入:首先嚐試將指紋 f
插入到位置 i1
對應的桶中。如果桶中有空位,則直接插入。如果 i1
的桶滿了,再嘗試將 f
插入到位置 i2
的桶中。如果 i2
的桶有空位,則直接插入。
踢出和重插入:如果兩個候選位置的桶都滿了,就需要“踢出”其中一個桶中的已有指紋,並將被踢出的指紋重新插入。具體步驟如下:
-
從
i1
或i2
中隨機選擇一個桶,將該桶中的一個現有指紋f'
踢出。 -
將原始指紋
f
插入到踢出的指紋f'
的位置。 -
對於被踢出的指紋
f'
,計算它的另一個候選位置i2'
,即:
$$
i2′=i1′⊕h(f′)
$$ -
將被踢出的指紋
f'
重新插入到i2'
的桶中。 -
如果
i2'
的桶也滿了,重複踢出和重插入的過程,直到成功插入或者達到最大重插入次數。
相信大家看下來可能有點模糊模糊,下面便一一解答
具體的指紋是怎麼取的?這個指紋算出來是什麼樣子的?
指紋是透過hash函式來獲得,如果說我們hash函式算出的值過長那麼就截斷
例如:
$$
f=h(x)&0xFF
$$
通常會選用8位和16位
為什麼第二個hash函式需要是i1⊕h(f)?以及為什麼他的傳參要是f?
先給出結論主要是為了實現搬家
首先回到上面的重插入!當位置衝突後我們最終會算出裡面那個value最終放的是指紋對吧?然後對應的雜湊函式我們都知道對吧?假如說原本這個位置新增的選擇的是i1,那麼他接下來要選擇的是i2對吧?那麼i2=i1⊕h(f),這裡面什麼我們都有沒錯吧?那麼我們自然可以實現讓它去搬家的操作,(同時這裡也有印證了那麼一句名言,軟體上有問題那麼再加一層就沒有問題!偉大無需多言)
查詢
布穀鳥過濾器的查詢過程很簡單,給定一個項x,演算法首先根據上述插入公式,計算x的指紋和兩個候選桶。然後讀取這兩個桶:如果兩個桶中的任何現有指紋匹配,則布穀鳥過濾器返回true,否則過濾器返回false。此時,只要不發生桶溢位,就可以確保沒有假陰性。(這句話的意思是:我們在插入的時候可能會有踢出的操作嘛,那麼在桶不夠的情況下,再踢出後你的指紋就被踢出了呀,然後我們在判斷的時候會出現我們原本新增了但是現在結果是找不到)
仍然存在假陽性哦,基本上跟hash有關都有一定的假陽性,我們可能出現hash對映到同樣兩個桶對吧,那麼在他的指紋計算也相同時,不就是假陽性了嗎?
刪除
很容易啦,我們關鍵就是在對應x的槽上放了個x對應的指紋,指紋刪掉就ok了
支援擴容(普通布穀鳥是不行的,普通bloom也是不行的)
這裡基本上要涉及論文了,我懶得看了..,講述我個人的猜測,不過別人的應該不會這麼簡單
我的猜測就是把原先能得出hash函式的得出的指紋來當做新的要設定的值,因為我們透過得出指紋的hash得到的值應該是與原本的空間是無關的,那麼也就是說這裡可以當做不變數,且上文的查詢操作也指出其實關鍵是驗證指紋是否相同在這兩個坑中,所以這個指紋也本身具有一定的特性,有點類似責任鏈的思想,弄了個hash鏈,不過碰撞的機率應該也不會提升太大
缺點
- 刪除不完美,存在誤刪的機率。刪除的時候知識刪除了一份指紋副本,並不能確定此指紋副本是要刪除的key的指紋。同時這個問題也導致了假陽性的情況。
- 插入次數可能會有多次
參考
https://www.cnblogs.com/zhaodongge/p/15067657.html
https://github.com/CGCL-codes/DCF