如何高效判斷元素w是否存在於集合A之中?首先想到的答案是,把集合A中的元素一個個放到哈希表中,而後在哈希表中查一下w便可。這樣確實能夠解決小數據量場景下元素存在性斷定,但若是A中元素數量巨大,甚至數據量遠遠超過機器內存空間,該如何解決問題呢?算法
實現一個基於磁盤和內存的哈希索引固然能夠解決這個問題。而另外一種低成本的方式就是藉助布隆過濾器(Bloom Filter)來實現。數組
布隆過濾器由一個長度爲N的01數組array組成。首先將數組array每一個元素初始設爲0。對集合A中的每一個元素w,作K次哈希,第i次哈希值對N取模獲得一個index(i),即index(i)=HASH_i(w)%N,將array數組中的array[index(i)]置爲1。最終array變成一個某些元素爲1的01數組。性能
下面舉個例子,如圖所示,A={x, y, z},N=18,K=3。this
初始化array=000000000000000000。對元素x,HASH_0(x)%N=1,HASH_1(x)%N=5,HASH_2(x)%N=13。
所以array=010001000000010000。對元素y,HASH_0(y)%N=4,HASH_1(y)%N=11,HASH_2(y)%N=16。
所以array=010011000001010010。對元素z,HASH_0(z)%N=3,HASH_1(y)%N=5,HASH_2(y)%N=11。
所以array=010111000001010010。最終獲得的布隆過濾器串爲:010111000001010010。spa
此時,對於元素w,K次哈希值分別爲:
HASH_0(w)%N=4
HASH_1(w)%N=13
HASH_2(w)%N=15
能夠發現,布隆過濾器串中的第15位爲0,所以能夠確認w確定不在集合A中。
由於若w在A中,則第15位一定爲1。設計
若是有另一個元素t,K次哈希值分別爲:
HASH_0(t)%N=5
HASH_1(t)%N=11
HASH_2(t)%N=13
咱們發現布隆過濾器串中的第五、十一、13位都爲1,可是卻無法確定t必定在集合A中。code
所以,布隆過濾器串對任意給定元素w,給出的存在性結果爲兩種:blog
•w可能存在於集合A中。
•w確定不在集合A中。索引
當N取K*|A|/ln2時(其中|A|表示集合A元素個數),能保證最佳的誤判率,所謂誤判率也就是過濾器斷定元素可能在集合中但實際不在集合中的佔比。內存
舉例來講,若集合有20個元素,K取3時,則設計一個N=3×20/ln2=87二進制串來保存布隆過濾器比較合適。
布隆過濾器的代碼實現很短,以下所示:
public class BloomFilter { private int k; private int bitsPerKey; private int bitLen; private byte[] result; public BloomFilter(int k,int bitsPerKey) { this.k = k; this.bitsPerKey = bitsPerKey; } public byte[] generate(byte[][] keys) { assert keys != null; bitLen=keys.length * bitsPerKey; bitLen=((bitLen+7) / 8) << 3; bitLen=bitLen < 64 ? 64 : bitLen; result = new byte[bitLen >> 3]; for ( int i=0; i < keys.length;i++){ assert keys[i] != null; int h = Bytes.hash(keys[i]); for (int t=0; t < k; t++){ int idx = (h % bitLen + bitLen) % bitLen; result[idx / 8] |= (1 << (idx % 8)); int delta=(h >> 17) | (h << 15); h += delta; } } return result; } public boolean contains(byte[] key) { assert result != null; int h=Bytes.hash(key); for (int t=0; t < k; t++) { int idx = ( h % bitLen + bitLen) % bitLen; if ((result[idx / 8] & (1 << (idx % 8))) == 0) { return false; } int delta=(h >> 17) | (h << 15); h += delta; } return true; } }
有兩個地方說明一下:
•在構造方法BloomFilter(int k, int bitsPerKey)中,k表示每一個Key哈希的次數,bitsPerkey表示每一個Key佔用的二進制bit數,如有x個Key,則N=x*bitsPerKey。
•在實現中,對Key作k次哈希時,算出第一次哈希值h以後,可藉助h位運算來實現二次哈希,甚至三次哈希。這樣性能會比較好。
有了布隆過濾器這樣一個存在性判斷以後,咱們回到最開始提到的案例。把集合A的元素按照順序分紅若干個塊,每塊不超過64KB,每塊內的多個元素都算出一個布隆過濾器串,多個塊的布隆過濾器組成索引數據。爲了判斷元素w是否存在於集合A中,先對w計算每個塊的布隆過濾器串的存在性結果,若結果爲確定不存在,則繼續判斷w是否可能存在於下一個數據塊中。若結果爲可能存在,則讀取對應的數據塊,判斷w是否在數據塊中,若存在則表示w存在於集合A中;若不存在則繼續判斷w是否在下一個數據塊中。這樣就解決了這個問題。
正是因爲布隆過濾器只需佔用極小的空間,即可給出「可能存在」和「確定不存在」的存在性判斷,所以能夠提早過濾掉不少沒必要要的數據塊,從而節省了大量的磁盤IO。HBase的Get操做就是經過運用低成本高效率的布隆過濾器來過濾大量無效數據塊的,從而節省大量磁盤IO。
在HBase 1.x版本中,用戶能夠對某些列設置不一樣類型的布隆過濾器,共有3種類型。
• NONE:關閉布隆過濾器功能。
• ROW:按照rowkey來計算布隆過濾器的二進制串並存儲。Get查詢的時候,必須帶rowkey,因此用戶能夠在建表時默認把布隆過濾器設置爲ROW類型。
• ROWCOL:按照rowkey+family+qualif ier這3個字段拼出byte[]來計算布隆過濾器值並存儲。若是在查詢的時候,Get能指定rowkey、family、qualifier這3個字段,則確定能夠經過布隆過濾器提高性能。可是若是在查詢的時候,Get中缺乏rowkey、family、qualif ier中任何一個字段,則沒法經過布隆過濾器提高性能,由於計算布隆過濾器的Key不肯定。
文章基於《HBase原理與實踐》一書