關注公衆號:xy的技術圈git
在項目開發中,咱們常常會遇到去重問題。好比:判斷一我的有沒有瀏覽過一篇文章,判斷一我的當天是否登陸過某個系統,判斷一個ip是否發過一個請求,等等。github
比較容易想到的是使用set來實現這個功能。但若是數據量較大,使用set會很是消耗內存,性能也不高。在前面的文章中,咱們介紹了一種數據結構:BitMap來提升性能。但BitMap仍然比較消耗內存,尤爲是在數據比較稀疏的狀況下,使用BitMap並不划算。算法
實際上,對於「去重」問題,業界有另一個更優秀的數據結構來解決這類問題,那就是——布隆過濾器(BloomFilter)。docker
布隆過濾器與BitMap相似,底層也是一個位數組。1表示有,0表示無。但布隆過濾器比BitMap須要更少的內存,它是怎麼辦到的呢?答案是多個hash。數組
咱們知道hash算法,是把一個數從較大範圍的值,映射到較小範圍值。好比咱們有一個10位的數組,使用某個hash算法及其數組上的表示:bash
hash(「xy」) = 3;數據結構
hash(「技術圈」) = 5;函數
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0post
這樣,咱們使用這個hash算法就能快速的判斷一個字符串是否是存在一個集合裏面了。但衆所周知,hash算法是有可能發生hash衝突的。好比可能有兩個不一樣的字符串映射到同一個數:性能
hash(「xy」) = 3;
hash(「xy的技術圈」) = 3;
這種狀況下,就不能準確得判斷出某個字符串是否是存在於集合之中呢。
那怎麼解決這個問題呢?答案是使用多個不一樣的hash算法。好比:
h1(「xy」) = 3, h2(「xy」) = 5, h3(「xy」) = 7;
h1(「技術圈」) = 5, h2(「技術圈」) = 6, h3(「技術圈」) = 7;
h1(「xy的技術圈」) = 3, h2(「xy的技術圈」) = 6, h3(「xy的技術圈」) = 9;
最開始,集合裏沒有元素,全部位都是0:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
而後,插入「xy」,利用屢次hash,把每次hash的結果下標3, 5, 7都插入到相應的地方:
0, 0, 0, 1, 0, 1, 0, 1, 0, 0
而後,插入「技術圈」,利用屢次hash,把每次hash的結果下標5, 6, 7都插入到相應的地方,已是1的下標不變:
0, 0, 0, 1, 0, 1, 1, 1, 0, 0
這個時候,若是想要判斷「xy」是否在集合中,只須要使用一樣的3個hash算法,來計算出下標是3, 5, 7,發現這3個下標都爲1,那麼就認爲「xy」這個字符串在集合中。而「xy的技術圈」計算出來的下標是3, 6, 9。發現這三個下標有不是1的地方,好比下標爲9的地方是0,那就說明「xy的技術圈」這個字符串還不在集合中。
從原理能夠看得出來,布隆過濾器是有可能存在必定的偏差的。尤爲是當hash函數比較少的時候。布隆過濾器是根據屢次hash計算下標後,數組的這些下標是否都爲1來判斷這個元素是否存在的。因此是存在必定的概率,要檢查的元素實際上沒有插入,但被其它元素插入影響,致使全部下標都爲1。
因此布隆過濾器不能刪除,由於一旦刪除(即將相應的位置爲0),就很大可能會影響其餘元素。
若是使用布隆過濾器判斷一個函數是否存在於一個集合,若是它返回true,則表明可能存在。若是它返回false,則表明必定不存在。
因而可知,布隆過濾器適合於一些須要去重,但不必定要徹底精確的場景。好比:
相應的,布隆過濾器不適合一些要求零偏差的場景,好比:
這就是布隆過濾器的基本原理。由上面的例子能夠看出來,若是空間越大,hash函數越多,結果就越精確,但空間效率和查詢效率就會越低。
這裏有一個測試數據:
後面4列中的數據就是發生偏差的數量。可見,空間大小和集合大小不變的狀況下,增長hash函數能夠顯著減少偏差。但一旦集合大小達到空間大小的25%左右後,增長hash函數帶來的提神效果並不明顯。這個時候應該增長空間大小。
Redis的布隆過濾器不是原生自帶的,而是要經過module加載進去。Redis在4.0的版本中加入了module功能。具體使用能夠直接看RedisBloom github的README:github.com/RedisBloom/…
Redis的布隆過濾器主要有兩個命令:
bf.add
添加元素到布隆過濾器中:bf.add strs xy
bf.exists
判斷某個元素是否在過濾器中:bf.exists strs xy
Redis中有一個命令能夠來設置布隆過濾器的準確率:
bf.reserve strs 0.01 100
複製代碼
三個參數的含義:
error_rate
的值:容許布隆過濾器的錯誤率。initial_size
的值:初始化位數組的大小。若是你的項目沒有使用Redis,那可使用一些開源庫,基於代碼實現,直接存放在內存。好比Google的guava包中提供了BloomFilter
類,有興趣的讀者能夠去了解一下,研究研究源碼和使用。
RedisBloom模塊還實現了布穀鳥過濾器,它算是對布隆過濾器的加強版。解決了布隆過濾器的一些比較明顯的缺點,好比:不能刪除元素,不能計數等。除此以外,布穀鳥過濾器不用使用多個hash函數,因此查詢性能更高。除此以外,在相同的誤判率下,布穀鳥過濾器的空間利用率要明顯高於布隆,空間上大概能節省40%多。
筆者我的以爲,對於大多數場景來講,布隆過濾器足以解決咱們的問題。掘金上有一篇深度分析布穀鳥過濾器的文章,有興趣的讀者能夠去了解一下:juejin.im/post/5cfb9c…
認真寫文章,用心作分享。
我的網站:yasinshaw.com
公衆號:xy的技術圈