布隆過(guò)濾器的原理以及java 簡(jiǎn)單實(shí)現(xiàn)
一.布隆過(guò)濾器
布隆過(guò)濾器(Bloom Filter)是1970年由布隆提出的。它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都遠(yuǎn)遠(yuǎn)超過(guò)一般的算法,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。
如果想判斷一個(gè)元素是不是在一個(gè)集合里,一般想到的是將集合中所有元素保存起來(lái),然后通過(guò)比較確定。鏈表、樹(shù)、散列表(又叫哈希表,Hash table)等等數(shù)據(jù)結(jié)構(gòu)都是這種思路。但是隨著集合中元素的增加,我們需要的存儲(chǔ)空間越來(lái)越大。同時(shí)檢索速度也越來(lái)越慢,上述三種結(jié)構(gòu)的檢索時(shí)間復(fù)雜度分別為:O(n), O(log n), O(n/k)。
布隆過(guò)濾器的原理是,當(dāng)一個(gè)元素被加入集合時(shí),通過(guò)K個(gè)Hash函數(shù)將這個(gè)元素映射成一個(gè)位數(shù)組中的K個(gè)點(diǎn),把它們置為1。檢索時(shí),我們只要看看這些點(diǎn)是不是都是1就(大約)知道集合中有沒(méi)有它了:如果這些點(diǎn)有任何一個(gè)0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過(guò)濾器的基本思想。
布隆過(guò)濾器數(shù)據(jù)結(jié)構(gòu)
布隆過(guò)濾器是一個(gè) bit 向量或者說(shuō) bit 數(shù)組,長(zhǎng)這樣:
如果我們要映射一個(gè)值到布隆過(guò)濾器中,我們需要使用多個(gè)不同的哈希函數(shù)生成多個(gè)哈希值,并對(duì)每個(gè)生成的哈希值指向的 bit 位置 1,例如針對(duì)值 “baidu” 和三個(gè)不同的哈希函數(shù)分別生成了哈希值 1、4、7,則上圖轉(zhuǎn)變?yōu)椋?/p>
值得注意的是,4 這個(gè) bit 位由于兩個(gè)值的哈希函數(shù)都返回了這個(gè) bit 位,因此它被覆蓋了?,F(xiàn)在我們?nèi)绻氩樵?“dianping” 這個(gè)值是否存在,哈希函數(shù)返回了 1、5、8三個(gè)值,結(jié)果我們發(fā)現(xiàn) 5 這個(gè) bit 位上的值為 0,說(shuō)明沒(méi)有任何一個(gè)值映射到這個(gè) bit 位上,因此我們可以很確定地說(shuō) “dianping” 這個(gè)值不存在。而當(dāng)我們需要查詢 “baidu” 這個(gè)值是否存在的話,那么哈希函數(shù)必然會(huì)返回 1、4、7,然后我們檢查發(fā)現(xiàn)這三個(gè) bit 位上的值均為 1,那么我們可以說(shuō) “baidu” 存在了么?答案是不可以,只能是 “baidu” 這個(gè)值可能存在。
這是為什么呢?答案跟簡(jiǎn)單,因?yàn)殡S著增加的值越來(lái)越多,被置為 1 的 bit 位也會(huì)越來(lái)越多,這樣某個(gè)值 “taobao” 即使沒(méi)有被存儲(chǔ)過(guò),但是萬(wàn)一哈希函數(shù)返回的三個(gè) bit 位都被其他值置位了 1 ,那么程序還是會(huì)判斷 “taobao” 這個(gè)值存在。
支持刪除么
目前我們知道布隆過(guò)濾器可以支持 add 和 isExist 操作,那么 delete 操作可以么,答案是不可以,例如上圖中的 bit 位 4 被兩個(gè)值共同覆蓋的話,一旦你刪除其中一個(gè)值例如 “tencent” 而將其置位 0,那么下次判斷另一個(gè)值例如 “baidu” 是否存在的話,會(huì)直接返回 false,而實(shí)際上你并沒(méi)有刪除它。
如何解決這個(gè)問(wèn)題,答案是計(jì)數(shù)刪除。但是計(jì)數(shù)刪除需要存儲(chǔ)一個(gè)數(shù)值,而不是原先的 bit 位,會(huì)增大占用的內(nèi)存大小。這樣的話,增加一個(gè)值就是將對(duì)應(yīng)索引槽上存儲(chǔ)的值加一,刪除則是減一,判斷是否存在則是看值是否大于0。
代碼簡(jiǎn)單實(shí)現(xiàn)布隆過(guò)濾器
package com.jd.demo.test; import java.util.Arrays; import java.util.BitSet; import java.util.concurrent.atomic.AtomicBoolean; public class MyBloomFilter { //你的布隆過(guò)濾器容量 private static final int DEFAULT_SIZE = 2 << 28; //bit數(shù)組,用來(lái)存放結(jié)果 private static BitSet bitSet = new BitSet(DEFAULT_SIZE); //后面hash函數(shù)會(huì)用到,用來(lái)生成不同的hash值,可隨意設(shè)置,別問(wèn)我為什么這么多8,圖個(gè)吉利 private static final int[] ints = {1, 6, 16, 38, 58, 68}; //add方法,計(jì)算出key的hash值,并將對(duì)應(yīng)下標(biāo)置為true public void add(Object key) { Arrays.stream(ints).forEach(i -> bitSet.set(hash(key, i))); } //判斷key是否存在,true不一定說(shuō)明key存在,但是false一定說(shuō)明不存在 public boolean isContain(Object key) { boolean result = true; for (int i : ints) { //短路與,只要有一個(gè)bit位為false,則返回false result = result && bitSet.get(hash(key, i)); } return result; } //hash函數(shù),借鑒了hashmap的擾動(dòng)算法 private int hash(Object key, int i) { int h; return key == null ? 0 : (i * (DEFAULT_SIZE - 1) & ((h = key.hashCode()) ^ (h >>> 16))); } }
測(cè)試
public static void main(String[] args) { MyNewBloomFilter myNewBloomFilter = new MyNewBloomFilter(); myNewBloomFilter.add("張學(xué)友"); myNewBloomFilter.add("郭德綱"); myNewBloomFilter.add(666); System.out.println(myNewBloomFilter.isContain("張學(xué)友"));//true System.out.println(myNewBloomFilter.isContain("張學(xué)友 "));//false System.out.println(myNewBloomFilter.isContain("張學(xué)友1"));//false System.out.println(myNewBloomFilter.isContain("郭德綱"));//true System.out.println(myNewBloomFilter.isContain(666));//true System.out.println(myNewBloomFilter.isContain(888));//false }
二.具體代碼使用
在實(shí)際應(yīng)用當(dāng)中,我們不需要自己去實(shí)現(xiàn)BloomFilter??梢允褂肎uava提供的相關(guān)類(lèi)庫(kù)即可。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>12345
判斷一個(gè)元素是否在集合中
public class Test1 { private static int size = 1000000; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size); public static void main(String[] args) { for (int i = 0; i < size; i++) { bloomFilter.put(i); } long startTime = System.nanoTime(); // 獲取開(kāi)始時(shí)間 //判斷這一百萬(wàn)個(gè)數(shù)中是否包含29999這個(gè)數(shù) if (bloomFilter.mightContain(29999)) { System.out.println("命中了"); } long endTime = System.nanoTime(); // 獲取結(jié)束時(shí)間 System.out.println("程序運(yùn)行時(shí)間: " + (endTime - startTime) + "納秒"); } }
運(yùn)行結(jié)果如下:
命中了 程序運(yùn)行時(shí)間: 441616納秒
自定義錯(cuò)誤率
public class Test3 { private static int size = 1000000; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01); public static void main(String[] args) { for (int i = 0; i < size; i++) { bloomFilter.put(i); } List<Integer> list = new ArrayList<Integer>(1000); // 故意取10000個(gè)不在過(guò)濾器里的值,看看有多少個(gè)會(huì)被認(rèn)為在過(guò)濾器里 for (int i = size + 10000; i < size + 20000; i++) { if (bloomFilter.mightContain(i)) { list.add(i); } } System.out.println("誤判的數(shù)量:" + list.size()); } }
運(yùn)行結(jié)果如下:
誤判的數(shù)量:941
對(duì)于緩存宕機(jī)的場(chǎng)景,使用白名單或者布隆過(guò)濾器都有可能會(huì)造成一定程度的誤判。原因是除了Bloom Filter 本身有誤判率,宕機(jī)之前的緩存不一定能覆蓋到所有DB中的數(shù)據(jù),當(dāng)宕機(jī)后用戶請(qǐng)求了一個(gè)以前從未請(qǐng)求的數(shù)據(jù),這個(gè)時(shí)候就會(huì)產(chǎn)生誤判。當(dāng)然,緩存宕機(jī)時(shí)使用白名單/布隆過(guò)濾器作為應(yīng)急的方式,這種情況應(yīng)該也是可以忍受的。
以上就是布隆過(guò)濾器的原理以及java 簡(jiǎn)單實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于java 布隆過(guò)濾器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之教室預(yù)訂管理系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+SpringBoot+Maven+Vue+mysql開(kāi)發(fā)的教室預(yù)訂管理系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有教室預(yù)訂管理該有的所有功能,感興趣的朋友快來(lái)看看吧2022-02-02使用java?實(shí)現(xiàn)mqtt兩種常用方式
在開(kāi)發(fā)MQTT時(shí)有兩種方式一種是使用Paho Java 原生庫(kù)來(lái)完成,一種是使用spring boot 來(lái)完成,這篇文章主要介紹了使用java?實(shí)現(xiàn)mqtt兩種方式,需要的朋友可以參考下2022-11-11springboot應(yīng)用訪問(wèn)zookeeper的流程
這篇文章主要介紹了springboot應(yīng)用訪問(wèn)zookeeper的流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01java+testng+selenium的自動(dòng)化測(cè)試實(shí)例
這篇文章主要介紹了java+testng+selenium的自動(dòng)化測(cè)試實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11springboot集成redis實(shí)現(xiàn)簡(jiǎn)單秒殺系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了springboot集成redis實(shí)現(xiàn)簡(jiǎn)單秒殺系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12SpringBoot添加SSL證書(shū),開(kāi)啟HTTPS方式(單向認(rèn)證服務(wù)端)
這篇文章主要介紹了SpringBoot添加SSL證書(shū),開(kāi)啟HTTPS方式(單向認(rèn)證服務(wù)端),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03