布隆過濾器是否好用,得看哈希函數寫成啥樣

語言: CN / TW / HK

作者:小傅哥
博客:http://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!😄

一、前言

布隆過濾器的歷史

布隆過濾器由 Burton Howard Bloom 於 1970 年提出,它是一種節省空間的概率數據結構,包括一個很長的二進制向量和一些列隨機映射函數。

二、布隆過濾器結構

布隆過濾器是一個基於數組和哈希函數散列元素的結構,很像HashMap的哈希桶。布隆過濾器可以用於檢測一個元素是否在集合中。它的優點是空間效率和查詢時間比一般算法要好很多,但也有一定概率的誤判性。如HashMap出現哈希碰撞💥

  • 趙敏:無忌,成昆上了光明頂!
  • 張無忌:咱們過濾器年久失修,已經不準了!
  • 張無忌:布隆過濾器的長度太小,哈希計算單一。導致謝飛機、拎瓢衝、成昆,三個人的哈希值都是相同的,所以沒法判斷成昆是否上了光明頂。咱們只能快些上山了,沿途小心。
  • 楊左使:老大,我現在就去維修一下。布隆過濾器的優化方式可以通過增加長度和多樣新計算哈希解決。

三、布隆過濾器實現

布隆過濾器的實現條件包括可以存放二進制元素的 BitSet 以及多樣性的哈希計算函數。

```java public class BloomFilter {

private static final HashGenerator.HashGroup[] GROUPS = new HashGenerator.HashGroup[]{HashGenerator.HashGroup.G1, HashGenerator.HashGroup.G2, HashGenerator.HashGroup.G3, HashGenerator.HashGroup.G4};

private final BitSet bits;

private HashGenerator[] generators;

} ```

所有的元素存放都經過多樣的哈希計算存放到 BitSet 中,這樣可以儘可能的分散元素,減少誤判性。

1. 哈希函數

```java private int hashG1(String value) { int hash = 0; for (int idx = 0; idx < value.length(); idx++) { char c = value.charAt(idx); hash = (hash << 5) + hash + c; hash &= hash; hash = Math.abs(hash); } return hash % (seed * size - 1); }

private int hashG2(String value) { int hash = 7397; for (int idx = 0; idx < value.length(); idx++) { char c = value.charAt(idx); hash = (hash << 5) + hash + c; } return Math.abs(hash % seed * (size - 1)); }

private int hashG3(String value) { int hash = 0; for (int idx = 0; idx < value.length(); idx++) { char c = value.charAt(idx); hash = (hash << 5) + hash + c; hash += c; hash &= hash; } return Math.abs(hash % (seed * size - 1)); }

private int hashG4(String value) { int h; return (value == null) ? 0 : Math.abs(seed * (size - 1) & ((h = value.hashCode()) ^ (h >>> 16))); } ```

  • 這裏提供了四種哈希計算的方式,相當於每一個哈希計算都是一次擾動處理。一個元素的存放可以經過四次哈希,儘量讓元素值做到散列。

2. 構建容器

java public BloomFilter(int size, int[] seeds) { bits = new BitSet(size); generators = new HashGenerator[seeds.length]; for (int i = 0; i < seeds.length; i++) { generators[i] = new HashGenerator(size, seeds[i], GROUPS[i % GROUPS.length]); } }

  • 構造函數根據所需創建的容器大小和哈希種子來初始化布隆過濾器。

3. 添加元素

java public void add(String value) { for (HashGenerator generator : generators) { int hash = generator.doHash(value); bits.set(hash, true); } }

  • 添加元素時按照元素初始化時的哈希計算種類,獲取哈希並存放。

4. 比對元素

java public boolean contains(String value) { boolean ret = true; for (HashGenerator generator : generators) { ret = ret && bits.get(generator.doHash(value)); } return ret; }

  • 比對元素時用的是同一類哈希計算方式,並且把這些哈希值 && 計算。用N個比特位置記錄一個值更準確

四、布隆過濾器測試

單元測試

java @Test public void test() { String val00 = "小傅哥"; String val01 = "http://bugstack.cn"; String val02 = "http://github.com/fuzhengwei/CodeGuide"; String val03 = "http://github.com/fuzhengwei"; BloomFilter filter = new BloomFilter(Integer.MAX_VALUE, new int[]{7, 19, 43, 77}); filter.add(val00); filter.add(val01); filter.add(val02); logger.info("測試結果 val00:{} 布隆過濾器:{}", val00, filter.contains(val00)); logger.info("測試結果 val01:{} 布隆過濾器:{}", val01, filter.contains(val01)); logger.info("測試結果 val02:{} 布隆過濾器:{}", val02, filter.contains(val02)); logger.info("測試結果 val02:{} 布隆過濾器:{}", val03, filter.contains(val03)); }

  • 可以看到這裏初始化了一個比較大的布隆過濾器,並且提供了4個隨機種子;7, 19, 43, 77計算哈希值。

測試結果

```java 21:33:22.790 [main] INFO bloom_filter.test.BloomFilterTest - 測試結果 val00:小傅哥 布隆過濾器:true 21:33:22.794 [main] INFO bloom_filter.test.BloomFilterTest - 測試結果 val01:http://bugstack.cn 布隆過濾器:true 21:33:22.794 [main] INFO bloom_filter.test.BloomFilterTest - 測試結果 val02:http://github.com/fuzhengwei/CodeGuide 布隆過濾器:true 21:33:22.795 [main] INFO bloom_filter.test.BloomFilterTest - 測試結果 val02:http://github.com/fuzhengwei 布隆過濾器:false

Process finished with exit code 0 ```

  • 通過測試可以看到,存放的val00、val01、val02 分別可以驗證出 true 沒有存放的 val03 驗證為fasle

五、常見面試題

  • 布隆過濾器的使用場景?
  • 布隆過濾器的實現原理和方式?
  • 如何提高布隆過濾器的準確性?
  • 有哪些中哈希計算方式?
  • 都有哪些類型的布隆過濾器實現?Google 開源的 Guava 中自帶的布隆過濾器、Redis 中的布隆過濾器(http://github.com/RedisBloom/RedisBloom)