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

作者:小傅哥
部落格://bugstack.cn

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

一、前言

布隆過濾器的歷史

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

二、布隆過濾器結構

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

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

三、布隆過濾器實現

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

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. 哈希函數

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. 構建容器

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. 添加元素

public void add(String value) {
    for (HashGenerator generator : generators) {
        int hash = generator.doHash(value);
        bits.set(hash, true);
    }
}
  • 添加元素時按照元素初始化時的哈希計算種類,獲取哈希並存放。

4. 比對元素

public boolean contains(String value) {
    boolean ret = true;
    for (HashGenerator generator : generators) {
        ret = ret && bits.get(generator.doHash(value));
    }
    return ret;
}
  • 比對元素時用的是同一類哈希計算方式,並且把這些哈希值 && 計算。用N個比特位置記錄一個值更準確

四、布隆過濾器測試

單元測試

@Test
public void test() {
    String val00 = "小傅哥";
    String val01 = "//bugstack.cn";
    String val02 = "//github.com/fuzhengwei/CodeGuide";
    String val03 = "//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計算哈希值。

測試結果

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


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

五、常見面試題

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