用 Java 實現(xiàn)一個可用的布隆過濾器(Bloom Filter)

原文地址:https://www.inlighting.org/archives/java-implement-bloom-filter/

布隆過濾器可以使用極少的空間來判斷一個元素是否存在某一個集合中,本文不具體討論布隆過濾器的原理,而是探討如何實現(xiàn)一個可用的布隆過濾器。

實現(xiàn)源代碼:https://github.com/Smith-Cruise/java-bloom-filter

本文代碼提取自 Apache ORC 項目。

基本概念

這里附帶一些鏈接,適合不了解布隆過濾器的人閱讀。

誤算率 False Positive

使用布隆過濾器時要注意:一個元素經(jīng)過計算不存在時,那它一定不存在該集合中。但是如果一個元素經(jīng)過計算存在時,它并不一定存在該集合中,所以其產(chǎn)生了一定的誤算率。

所以設計一個可用的布隆過濾器,必須要確保它的誤算率低。

這里我們令布隆過濾器 hasn 函數(shù)個數(shù)為 k,有 m 個 bits,期望插入數(shù)字個數(shù)為 n,誤算率為 FPP

最佳 hash 函數(shù)數(shù)量計算公式
k=\frac{m}{n}ln2,k\geq 0
k 為此值能夠確保誤算率最低。

最佳 bits 個數(shù)計算公式
m=-\frac{n \cdot ln{FPP}}{(ln2)^2}

想知道上面兩個公式如何推導的,可以參考:https://en.wikipedia.org/wiki/Bloom_filter#Optimal_number_of_hash_functions

這里我們假設希望誤算率維持在 5%,預期會有 100 個元素插入。
m=-\frac{100 \cdot ln{0.05}}{(ln2)^2} \approx 640

k=\frac{640}{100}ln2 \approx 4

故該布隆過濾器的大小應有 640 個 bits 和 4 個 hash 函數(shù)。

Hash 函數(shù)設計

一個好的 Hash 函數(shù)就是要降低碰撞率,這里我們使用 Murmur Hash 3 作為 Hash 函數(shù),它計算速度快,而且經(jīng)過廣泛的測試。

Hash 函數(shù)的實現(xiàn)已經(jīng)附帶在源碼中了。

Bitset

Java 作為一門高級語言,沒有直接提供傳統(tǒng)的 bit 操作。但是我們可以參考官方的 Bitset 源碼實現(xiàn),自己設計一個 Bitset。之所以不使用官方的 Bitset,是因為其過為復雜,沒有必要。

我們使用 long 數(shù)組來存放每一個 bit。在 Java 中,一個 long 占用 64 個 bit,這樣可以一次性多存點。

序列化

我們生成的布隆過濾器肯定要支持持久化到磁盤中,這里我們使用 protobuf 來作為序列化框架。序列化的 proto 文件代碼如下:

message BloomFilter {
  required uint32 numHashFunctions = 1;
  repeated fixed64 bitset = 2;
}

我們只要保存 hash 函數(shù)的個數(shù)和 bitset 即可。bitset 使用 fixed64 類型是因為 long 占用 64 個 bit,bitset 使用 repeated 修飾是因為其為一個 long 數(shù)組。

源碼食用方法

首先系統(tǒng)安裝完成 protoc,用于編譯 protobuf 文件。

在源碼目錄執(zhí)行命令 protoc --java_out=src/main/java src/main/resources/BloomFilter.proto

測試代碼如下,先生成一個布隆過濾器,填充值后序列化到磁盤中名為 bloom_filter 文件。然后讀取文件,檢測某個值是否添加過:

package org.inlighting;

import org.inlighting.proto.BloomFilterProtos;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class Test {

    private static final String STORE_PATH = "bloom_filter";

    public static void main(String[] args) throws IOException {

        // Serialize to bloom_filter to disk.
        {
            BloomFilter bloomFilter = new BloomFilter(10, 0.05);
            System.out.println(bloomFilter.toString());
            bloomFilter.addString("Hello World");
            bloomFilter.addLong(2L);
            bloomFilter.addLong(1);
            bloomFilter.add("ni".getBytes(StandardCharsets.UTF_8));
            BloomFilterProtos.BloomFilter.Builder builder = BloomFilterProtos.BloomFilter.newBuilder();
            BloomFilterIO.serialize(builder, bloomFilter);
            builder.build().writeTo(new FileOutputStream(STORE_PATH));
        }


        // read from bloom_filter
        {
            BloomFilterProtos.BloomFilter bloomFilterProtos =
                    BloomFilterProtos.BloomFilter.parseFrom(new FileInputStream(STORE_PATH));
            BloomFilter bloomFilter =  BloomFilterIO.deserialize(bloomFilterProtos);
            System.out.println(bloomFilter.testString("Hello World")); // true
            System.out.println(bloomFilter.testLong(2)); // true
            System.out.println(bloomFilter.testLong(2L)); // true
            System.out.println(bloomFilter.testLong(1)); // true
            System.out.println(bloomFilter.testLong(1L)); // true
            System.out.println(bloomFilter.test("ni".getBytes(StandardCharsets.UTF_8))); // true
            System.out.println(bloomFilter.test("hao".getBytes(StandardCharsets.UTF_8))); // false
            System.out.println(bloomFilter.testString("hello world")); // false
            System.out.println(bloomFilter.testLong(3)); // false
        }
    }
}

輸出結果:

numBits: 64, numHashFunctions: 4
true
true
true
true
true
true
false
false
false

生成的 BloomFilter 占用空間 11B,比較理想。

后序看有沒有必要把這個封裝成一個 jar 包上傳到 Maven 中央倉庫供調用。

結尾附帶一個 Bloom Filter 計算器,可以在線計算需要的 hash 函數(shù)的個數(shù)和空間:https://krisives.github.io/bloom-calculator/

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

推薦閱讀更多精彩內容