關於BloomFilter
先要了解什么是hash函數。
哈希函數
布隆過濾器離不開哈希函數,所以在這里有必要介紹下哈希函數的概念,如果你已經掌握了,可以直接跳到下一小節。
哈希函數的性質:
- 經典的哈希函數都有無限大的輸入值域(無窮大)。
- 經典的哈希函數的輸出域都是固定的范圍(有窮大,假設輸出域為S)
- 當給哈希函數傳入相同的值時,返回值必一樣
- 當給哈希函數傳入不同的輸入值時,返回值可能一樣,也可能不一樣。
- 輸入值會盡可能均勻的分布在S上
前三點都是哈希函數的基礎,第四點描述了哈希函數存在哈希碰撞的現象,因為輸入域無限大,輸出域有窮大,這是必然的,輸入域中會有不同的值對應到輸入域S中。第五點事評價一個哈希函數優劣的關鍵,哈希函數越優秀,分布就越均勻且與輸入值出現的規律無關。比如存在"hash1","hash2","hash3"三個輸入值比較類似,經過哈希函數計算后的結果應該相差非常大,可以通過常見的MD5和SHA1算法來驗證這些特性。如果一個優秀的函數能夠做到不同的輸入值所得到的返回值可以均勻的分布在S中,將其返回值對m取余(%m),得到的返回值可以認為也會均勻的分布在0~m-1位置上。
接下來開始介紹布隆過濾器。
有一個長度為m的bit型數組,如我們所知,每個位置只占一個bit,每個位置只有0和1兩種狀態。假設一共有k個哈希函數相互獨立,輸入域都為s且都大於等於m,那么對同一個輸入對象(可以想象為緩存中的一個key),經過k個哈希函數計算出來的結果也都是獨立的。對算出來的每一個結果都對m取余,然后在bit數組上把相應的位置設置為1(描黑),如下圖所示:
至此一個輸入對象對bit array集合的影響過程就結束了,我們可以看到會有多個位置被描黑,也就是設置為1.接下來所有的輸入對象都按照這種方式去描黑數組,最終一個布隆過濾器就生成了,它代表了所有輸入對象組成的集合。
那么如何判斷一個對象是否在過濾器中呢?假設一個輸入對象為hash1,我們需要通過看k個哈希函數算出k個值,然后把k個值取余(%m),就得到了k個[0,m-1]的值。然后我們判斷bit array上這k個值是否都為黑,如果有一個不為黑,那么肯定hash1肯定不在這個集合里。如果都為黑,則說明hash1在集合里,但有可能誤判。因為當輸入對象過多,而集合過小,會導致集合中大多位置都會被描黑,那么在檢查hash1時,有可能hash1對應的k個位置正好被描黑了,然后錯誤的認為hash1存在集合里。
控制布隆過濾器的誤判率
如果bit array集合的大小m相比於輸入對象的個數過小,失誤率就會變高。這里直接引入一個已經得到證明的公式,根據輸入對象數量n和我們想要達到的誤判率為p計算出布隆過濾器的大小m和哈希函數的個數k.
布隆過濾器的大小m公式:
哈希函數的個數k公式:
布隆過濾器真實失誤率p公式:
例子1:假設我們的緩存系統,key為userId,value為user。如果我們有10億個用戶,規定失誤率不能超過0.01%,通過計算器計算可得m=19.17n,向上取整為20n,也就是需要200億個bit,換算之后所需內存大小就是2.3G。通過第二個公式可計算出所需哈希函數k=14.因為在計算m的時候用了向上取整,所以真是的誤判率絕對小於等於0.01%。
例子2:n=100億,p=0.0001(萬分之一),m=131,571,428,572bit=131,571,428,572/8字節=22.3G,22.3G為實際空間,
Hash函數的個數K,,可得K= 0.7*m/n,可約得13個,那么真實失誤率p=6/十萬。
綜上:m的長度與失誤率p有關,m越大,失誤率p越小。
快速集成BloomFilter
關於布隆過濾器,我們不需要自己實現,谷歌已經幫我們實現好了。
- 引入依賴
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>
- 核心API
1 /** 2 * Creates a {@link BloomFilter BloomFilter<T>} with the expected number of 3 * insertions and expected false positive probability. 4 * 5 * <p>Note that overflowing a {@code BloomFilter} with significantly more elements 6 * than specified, will result in its saturation, and a sharp deterioration of its 7 * false positive probability. 8 * 9 * <p>The constructed {@code BloomFilter<T>} will be serializable if the provided 10 * {@code Funnel<T>} is. 11 * 12 * <p>It is recommended that the funnel be implemented as a Java enum. This has the 13 * benefit of ensuring proper serialization and deserialization, which is important 14 * since {@link #equals} also relies on object identity of funnels. 15 * 16 * @param funnel the funnel of T's that the constructed {@code BloomFilter<T>} will use 17 * @param expectedInsertions the number of expected insertions to the constructed 18 * {@code BloomFilter<T>}; must be positive 19 * @param fpp the desired false positive probability (must be positive and less than 1.0) 20 * @return a {@code BloomFilter} 21 */ 22 public static <T> BloomFilter<T> create( 23 Funnel<T> funnel, int expectedInsertions /* n */, double fpp) { 24 checkNotNull(funnel); 25 checkArgument(expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", 26 expectedInsertions); 27 checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp); 28 checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp); 29 if (expectedInsertions == 0) { 30 expectedInsertions = 1; 31 } 32 /* 33 * TODO(user): Put a warning in the javadoc about tiny fpp values, 34 * since the resulting size is proportional to -log(p), but there is not 35 * much of a point after all, e.g. optimalM(1000, 0.0000000000000001) = 76680 36 * which is less than 10kb. Who cares! 37 */ 38 long numBits = optimalNumOfBits(expectedInsertions, fpp); 39 int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); 40 try { 41 return new BloomFilter<T>(new BitArray(numBits), numHashFunctions, funnel, 42 BloomFilterStrategies.MURMUR128_MITZ_32); 43 } catch (IllegalArgumentException e) { 44 throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e); 45 } 46 }
1 /** 2 * Returns {@code true} if the element <i>might</i> have been put in this Bloom filter, 3 * {@code false} if this is <i>definitely</i> not the case. 4 */ 5 public boolean mightContain(T object) { 6 return strategy.mightContain(object, funnel, numHashFunctions, bits); 7 }
- 例子
1 public static void main(String... args){ 2 /** 3 * 創建一個插入對象為一億,誤報率為0.01%的布隆過濾器 4 */ 5 BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 100000000, 0.0001); 6 bloomFilter.put("121"); 7 bloomFilter.put("122"); 8 bloomFilter.put("123"); 9 System.out.println(bloomFilter.mightContain("121")); 10 }
Over...
參考: