Neo中的BloomFilter

布隆過濾器

布隆過濾器(英語:Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數(shù)。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優(yōu)點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。

布隆過濾器 (Bloom Filter)是一種space efficient的概率型數(shù)據(jù)結(jié)構(gòu),在垃圾郵件過濾的黑白名單方法、爬蟲(Crawler)的網(wǎng)址判重模塊中等等經(jīng)常被用到。哈希表也能用于判斷元素是否在集合中,但是布隆過濾器只需要哈希表的1/8或1/4的空間復(fù)雜度就能完成同樣的問題。布隆過濾器可以插入元素,但不可以刪除已有元素。其中的元素越多,false positive rate(誤報率)越大,但是false negative (漏報)是不可能的。

基本概念

如果想判斷一個元素是不是在一個集合里,一般想到的是將集合中所有元素保存起來,然后通過比較確定。鏈表散列表(又叫哈希表,Hash table)等等數(shù)據(jù)結(jié)構(gòu)都是這種思路。但是隨著集合中元素的增加,我們需要的存儲空間越來越大。同時檢索速度也越來越慢,上述三種結(jié)構(gòu)的檢索時間復(fù)雜度分別為 O(n),O(log n),O(n/k)

布隆過濾器的原理是,當一個元素被加入集合時,通過K個散列函數(shù)將這個元素映射成一個位數(shù)組中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。

算法描述

  1. 一個empty bloom filter是一個有m bits的bit array,每一個bit位都初始化為0。并且定義有k個不同的hash function,每個都以uniform random distribution將元素hash到m個不同位置中的一個。在下面的介紹中n為元素數(shù),m為布隆過濾器或哈希表的slot數(shù),k為布隆過濾器重hash function數(shù)。

  2. 為了add一個元素,用k個hash function將它hash得到bloom filter中k個bit位,將這k個bit位置1。

  3. 為了query一個元素,即判斷它是否在集合中,用k個hash function將它hash得到k個bit位。若這k bits全為1,則此元素在集合中;若其中任一位不為1,則此元素比不在集合中(因為如果在,則在add時已經(jīng)把對應(yīng)的k個bits位置為1)。

  1. 不允許remove元素,因為那樣的話會把相應(yīng)的k個bits位置為0,而其中很有可能有其他元素對應(yīng)的位。因此remove會引入false negative,這是絕對不被允許的。

  2. 當k很大時,設(shè)計k個獨立的hash function是不現(xiàn)實并且困難的。對于一個輸出范圍很大的hash function(例如MD5產(chǎn)生的128 bits數(shù)),如果不同bit位的相關(guān)性很小,則可把此輸出分割為k份。或者可將k個不同的初始值(例如0,1,2, … ,k-1)結(jié)合元素,feed給一個hash function從而產(chǎn)生k個不同的數(shù)。

  3. 當add的元素過多時,即n/m過大時(n是元素數(shù),m是bloom filter的bits數(shù)),會導(dǎo)致false positive過高,此時就需要重新組建filter,但這種情況相對少見。

優(yōu)點

相比于其它的數(shù)據(jù)結(jié)構(gòu),布隆過濾器在空間和時間方面都有巨大的優(yōu)勢。布隆過濾器存儲空間和插入/查詢時間都是常數(shù)(O(k))。另外,散列函數(shù)相互之間沒有關(guān)系,方便由硬件并行實現(xiàn)。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優(yōu)勢。

布隆過濾器可以表示全集,其它任何數(shù)據(jù)結(jié)構(gòu)都不能;

缺點

但是布隆過濾器的缺點和優(yōu)點一樣明顯。誤算率是其中之一。隨著存入的元素數(shù)量增加,誤算率隨之增加。但是如果元素數(shù)量太少,則使用散列表足矣。

另外,一般情況下不能從布隆過濾器中刪除元素。我們很容易想到把位數(shù)組變成整數(shù)數(shù)組,每插入一個元素相應(yīng)的計數(shù)器加1, 這樣刪除元素時將計數(shù)器減掉就可以了。然而要保證安全地刪除元素并非如此簡單。首先我們必須保證刪除的元素的確在布隆過濾器里面。這一點單憑這個過濾器是無法保證的。另外計數(shù)器回繞也會造成問題。

在降低誤算率方面,有不少工作,使得出現(xiàn)了很多布隆過濾器的變種。

舉例說明布隆過濾器的空間優(yōu)勢

先來一個結(jié)論:對于一個有1%誤報率和一個最優(yōu)k值的布隆過濾器來說,無論元素的類型及大小,每個元素只需要9.6 bits來存儲。這個優(yōu)點一部分繼承自array的緊湊性,一部分來源于它的概率性。如果你認為1%的誤報率太高,那么對每個元素每增加4.8 bits,我們就可將誤報率降低為原來的1/10。add和query的時間復(fù)雜度都為O(k),與集合中元素的多少無關(guān),這是其他數(shù)據(jù)結(jié)構(gòu)都不能完成的。k是hash函數(shù)的個數(shù)。

舉例: 現(xiàn)有1億個email的黑名單,元素的數(shù)量(即email列表)為 108。若采用布隆過濾器,取k=8(k為hash函數(shù)個數(shù))。因為n為1億,所以總共需要8*108。又因為在保證誤判率低(后面解釋)且k和m選取合適時,空間利用率為50%(后面會解釋),所以總空間為

空間優(yōu)勢

所需空間比上述哈希結(jié)構(gòu)或者數(shù)組小得多,并且誤判率在萬分之一以下。為什么可以這樣算,可以看下面。

誤判概率的證明和計算

該過程的詳細說明來自于這個文章http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,為了看懂求導(dǎo)過程,需要復(fù)習數(shù)學知識。

對某一特定bit位在一個元素由某特定hash function插入時沒有被置位為1的概率為:

則k個hash function中沒有一個對其置位的概率為:

如果插入了n個元素,但都未將其置位的概率為:

則此位被置位的概率為:

現(xiàn)在考慮query階段,若對應(yīng)某個待query元素的k bits全部置位為1,則可判定其在集合中。因此將某元素誤判的概率為:

由于

,并且1/m 當m很大時趨近于0,所以

現(xiàn)在計算對于給定的m和n,k為何值時可以使得誤判率最低。設(shè)誤判率為k的函數(shù)為:

設(shè)

則簡化為

因為等式右邊的底數(shù)上是函數(shù),指數(shù)上也是函數(shù),沒有方法求這樣組合函數(shù)的導(dǎo)數(shù),只能取對數(shù)之后,變成乘法。我們有兩個函數(shù)相乘的求導(dǎo)方法,求導(dǎo)的幾個方法可以看參考資料,有很好的視頻說明。

兩邊取對數(shù)得

兩邊對k求導(dǎo)得,這邊涉及到乘法求導(dǎo),對數(shù)求導(dǎo),冪函數(shù)求導(dǎo):

下面求最值,


紅圈中的等式是把兩邊看成xln(x)這種形式得到的,和該函數(shù)的單調(diào)性相關(guān)。數(shù)學上能不能這么操作我還不太清楚。數(shù)學好的大神可以留言解釋一下。

因此,即當

時誤判率最低,此時誤判率為:


可以看出若要使得誤判率≤1/2,則:

這說明了若想保持某固定誤判率不變,布隆過濾器的bit數(shù)m與被add的元素數(shù)n應(yīng)該是線性同步增加的。

設(shè)計和應(yīng)用布隆過濾器的方法

應(yīng)用時首先要先由用戶決定要add的元素數(shù)n和希望的誤差率P。這也是一個設(shè)計完整的布隆過濾器需要用戶輸入的僅有的兩個參數(shù),之后的所有參數(shù)將由系統(tǒng)計算,并由此建立布隆過濾器。

系統(tǒng)首先要計算需要的內(nèi)存大小m bits:

再由m,n得到hash function的個數(shù):

至此系統(tǒng)所需的參數(shù)已經(jīng)備齊,接下來add n個元素至布隆過濾器中,再進行query。
根據(jù)公式,當k最優(yōu)時:



因此可驗證當P=1%時,存儲每個元素需要9.6 bits:


而每當想將誤判率降低為原來的1/10,則存儲每個元素需要增加4.8 bits:


這里需要特別注意的是,9.6 bits/element不僅包含了被置為1的k位,還把包含了沒有被置為1的一些位數(shù)。此時的

才是每個元素對應(yīng)的為1的bit位數(shù)。

從而使得P(error)最小時,我們注意到:

中的
,即

此概率為某bit位在插入n個元素后未被置位的概率。因此,想保持錯誤率低,布隆過濾器的空間使用率需為50%。

Neo中的布隆過濾器

上面的內(nèi)容大部分抄襲http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,原作者寫的太好了,我只是加上一些我的理解,方便數(shù)學不好的道友理解。下面我們看看Neo中的Bloom Filter。

using System.Collections;
using System.Linq;

namespace Neo.Cryptography
{
    public class BloomFilter
    {
        private readonly uint[] seeds;
        private readonly BitArray bits;

        public int K => seeds.Length;

        public int M => bits.Length;

        public uint Tweak { get; private set; }

        public BloomFilter(int m, int k, uint nTweak, byte[] elements = null)
        {
            this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray();
            this.bits = elements == null ? new BitArray(m) : new BitArray(elements);
            this.bits.Length = m;
            this.Tweak = nTweak;
        }

        public void Add(byte[] element)
        {
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
                bits.Set((int)(i % (uint)bits.Length), true);
        }

        public bool Check(byte[] element)
        {
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
                if (!bits.Get((int)(i % (uint)bits.Length)))
                    return false;
            return true;
        }

        public void GetBits(byte[] newBits)
        {
            bits.CopyTo(newBits, 0);
        }
    }
}

前面講了這么多,代碼竟然這么短,分析分析。

  1. 構(gòu)造函數(shù)傳入了m(多少位),k(hash函數(shù)種類),這個和我們前面分析根據(jù)p(錯誤率),和n(要插入的元素)來構(gòu)造的思路不一樣。所以Neo的這個版本應(yīng)該是一個簡化版本,輸入的數(shù)據(jù)n應(yīng)該是有范圍的,具體的范圍我們后面運行整個區(qū)塊鏈的時候在觀察,現(xiàn)在不知道n的個數(shù)有多大。
  2. hash函數(shù)使用了Murmur32,然后傳入不同的seed模擬不同的hash函數(shù),這個是可以的。
  3. 使用linq,函數(shù)式編程代碼非常簡潔,這也是C#的一個優(yōu)勢啊。
  4. add,check函數(shù)都很容易看懂,確實實現(xiàn)很簡潔。

總結(jié)

Bloom Filter是牛逼的數(shù)據(jù)結(jié)構(gòu),因為有很多數(shù)學知識在里面,雖然代碼不長,但是能看完這篇文章的人,會感受到代碼之美。

參考資料

布隆過濾器
知乎里面關(guān)于e的討論
An Intuitive Guide To Exponential Functions & e
Bloom Filters - the math
布隆過濾器 (Bloom Filter) 詳解
What is MurmurHash3 seed parameter?
數(shù)學學習資料
各種字符串Hash函數(shù)
怎樣在 Markdown 中使用數(shù)學公式
對數(shù)求導(dǎo)公式
導(dǎo)數(shù)法求函數(shù)最值

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內(nèi)容