阿里高頻面試題:如何快速判斷元素是不是在集合里?
時間:2021-11-04 16:08:20
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]如何快速判斷一個元素是不是在一個集合里?這個題目是我最近面試的時候常問的一個問題,這個問題不同人都有很多不同的回答。今天想介紹一個很少有人會提及到的方案,那就是借助布隆過濾器。什么叫布隆過濾器布隆過濾器(BloomFilter)是一個叫做Bloom的老哥于1970年提出的。實際上...
如何快速判斷一個元素是不是在一個集合里?這個題目是我最近面試的時候常問的一個問題,這個問題不同人都有很多不同的回答。
今天想介紹一個很少有人會提及到的方案,那就是借助布隆過濾器。
什么叫布隆過濾器布隆過濾器(Bloom Filter)是一個叫做 Bloom 的老哥于1970年提出的。實際上可以把它看作由二進制向量(或者說位數(shù)組)和一系列隨機映射函數(shù)(哈希函數(shù))兩部分組成的數(shù)據(jù)結構。它的優(yōu)點是空間效率和查詢時間都比一般的算法要好的多,缺點是有一定的誤識別率和刪除困難。
實現(xiàn)原理先來一張圖布隆過濾器算法主要思想就是利用 n 個哈希函數(shù)進行 hash 過后,得到不同的哈希值,根據(jù) hash 映射到數(shù)組(這個數(shù)組的長度可能會很長很長)的不同的索引位置上,然后將相應的索引位上的值設置為1。判斷該元素是否出現(xiàn)在集合中,就是利用k個不同的哈希函數(shù)計算哈希值,看哈希值對應相應索引位置上面的值是否是1,如果有1個不是1,說明該元素不存在在集合中。但是也有可能判斷元素在集合中,但是元素不在,這個元素所有索引位置上面的1都是別的元素設置的,這就導致一定的誤判幾率(這就是為什么上面是活可能在一個集合中的根本原因,因為會存在一定的 hash 沖突)。注意:誤判率越低,相應的性能就會越低。
作用布隆過濾器是可以用于判斷一個元素是不是(可能)在一個集合里,并且相比于其它的數(shù)據(jù)結構,布隆過濾器在空間和時間方面都有巨大的優(yōu)勢。注意上面的一個詞:可能。這里先預留一個懸念,下文會詳細分析到。
使用場景
- 判斷給定數(shù)據(jù)是否存在
- 防止緩存穿透(判斷請求的數(shù)據(jù)是否有效避免直接繞過緩存請求數(shù)據(jù)庫)等等、郵箱的垃圾郵件過濾、黑名單功能等等。
具體實現(xiàn)看完了布隆過濾器的算法思想,那就開始具體的實現(xiàn)的講解。我先來舉個例子,假設有旺財和小強兩個字符串,他們分別經(jīng)過三次的 hash 算法,然后根據(jù) hash 的結果將對應的數(shù)組(假設數(shù)組長度為 16)的索引位置的值置為1,先來看下旺財這個詞組:旺財經(jīng)過三次 hash 過后,值分別為2,4,6 那么根據(jù)可以得到索引值分別為 2、4、6,于是就將該數(shù)組的索引(2、4、6)位置的值置為1,其余當做是0,現(xiàn)在假設需要查找旺財 ,同樣經(jīng)過這個三個hash 然后發(fā)現(xiàn)得到的索引 2、4、6對應的位置的值都為1,那么可以判斷旺財可能是存在的。接著有將小強插入到布隆過濾器中,實際的過程和上面的一樣,假設得到的下標是 1、3、5拋開旺財?shù)拇嬖?,小強此時是這樣子在布隆過濾器中的,結合旺財和小強實際的數(shù)組是這樣子的:現(xiàn)在有來一個數(shù)據(jù):9527,現(xiàn)在要求是判斷 9527 是否存在,假設9527 經(jīng)過三次 hash 過后得到的下標分別為:5、6、7。結果發(fā)現(xiàn)下標為 7 的位置的值為0,那么可以肯定的判斷出,9527 一定不存在。接著又來了一個?國產(chǎn)007,經(jīng)過三次 hash 過后得到的下標分別為:2、3、5,結果發(fā)現(xiàn) 2、3、5下標對應的值全是1,于是可以大致判斷出?國產(chǎn)007可能存在。但是實際上經(jīng)過我們剛剛的演示,國產(chǎn)007 根本就不存在,之所以 2、3、5 索引位置的值為1 ,那是因為其他的數(shù)據(jù)設置的。說到這里,不知道大家有沒有明白布隆過濾器的作用。
代碼的實現(xiàn)作為 java 程序員,我們真的是很幸福了,我們使用到很多的框架和工具,基本都被封裝好了,布隆過濾器,我們就使用 google 封裝好的工具類。首先添加依賴?
?
????
????guava
????
import?com.google.common.hash.Funnels;
import?java.nio.charset.Charset;
public?class?BloomFilterDemo?{
????public?static?void?main(String[]?args)?{
????????/**
?????????*?創(chuàng)建一個插入對象為一億,誤報率為0.01%的布隆過濾器
?????????*?不存在一定不存在
?????????*?存在不一定存在
?????????*?----------------
?????????*? Funnel 對象:預估的元素個數(shù),誤判率
?????????*? mightContain :方法判斷元素是否存在
?????????*/
????????BloomFilter
????????bloomFilter.put("死");
????????bloomFilter.put("磕");
????????bloomFilter.put("Redis");
????????System.out.println(bloomFilter.mightContain("Redis"));
????????System.out.println(bloomFilter.mightContain("Java"));
????}
}
實戰(zhàn)我們來模擬這樣的場景:通過布隆過濾器來解決緩存穿透。首先你的知道什么叫緩存穿透吧?緩存穿透是指用戶訪問一個緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù),因為緩存中不存在,所以就會去訪問數(shù)據(jù)庫,如果并發(fā)很高。很容易會擊垮數(shù)據(jù)庫那布隆過濾器是如何解決這個問題的呢?他的原理是這樣子的:將數(shù)據(jù)庫中所有的查詢條件,放入布隆過濾器中,當一個查詢請求過來時,先經(jīng)過布隆過濾器進行查,如果判斷請求查詢值存在,則繼續(xù)查;如果判斷請求查詢不存在,直接丟棄。其代碼如下:String?get(String?key)?{
????String?value?=?redis.get(key);?????
????if?(value??==?null)?{
????????if(!bloomfilter.mightContain(key)){
????????????return?null;?
????????}else{
????????????value?=?db.get(key);?
????????????redis.set(key,?value);?
????????}????
????}
????return?value;
}
小結本文詳細介紹了布隆過濾器是什么?有什么作用?實現(xiàn)原理以及從代碼層面多方面來闡述布隆過濾器。學習能為各位在學習進階的路上添磚加瓦。