日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【数据结构】布隆过滤器:BloomFilter原理及Java实现

發布時間:2024/3/12 java 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【数据结构】布隆过滤器:BloomFilter原理及Java实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

布隆過濾器(Bloom Filter)是一個叫做 Bloom 的大佬在1970年提出的。我們可以把它看做由二進制向量(或者說數組)和一系列隨機映射函數(哈希函數)兩部分組成的數據結構。相比于我們平時用的 List,Set,Map 等容器,它占用的空間更少且效率更高,但缺點是其返回的結果是概率性的,而不是非常準確的。理論情況下添加到集合中的元素越多,誤報的可能性就越大。并且,存放在布隆過濾器的數據不容易刪除。

它的基本思想是:當一個元素被加入集合時,通過 K 個散列函數將這個元素映射成一個位數組中的 K 個點,把它們置為1;檢索時,只要看看這些點是不是都是1 就(大約)知道集合中有沒有它了——如果這些點有任何一個0,則被檢元素一定不在,如果都是1,則被檢元素很可能在。

1.原理

布隆過濾器(Bloom Filter)核心實現是一個超大的位數組幾個哈希函數。如下圖所示,有一個大小為 12 的數組,然后有3個哈希函數:

1.1 流程分析

假如現在有一個 keyword 要放入某個容器了,現在要經過Bloom Filter,具體的操作流程如下:

  • 過濾器初始化:將位數組進行初始化,即數組每位都設置位0

  • 放入元素:

  • 對于集合里面的每一個元素,將元素依次通過3個哈希函數進行映射,每次映射都會產生一個哈希值
  • 每個計算得到的哈希值對應位數組上面的一個點,所以將位數組對應的位置標記為1
  • 判斷存在:查詢W元素是否存在集合中的時候,同樣的方法將W通過哈希映射到位數組上的3個點

    • 如果3個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中
    • 如果3個點都為1,則該元素可能存在集合中
  • 因為此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。

    1.2 誤差分析

    可以從圖中可以看到,向容器中 put 時,x1映射到(2/4/8),x2映射到(4/6/10):

    假設現在要判斷元素 W 是否在容器中,通過哈希函數計算出的結果是(4/6/8),雖然指定位都為1,但顯然沒有這個元素。因此這種情況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的原因。

    2.應用場景

    在很多場景下,我們都需要一個能迅速判斷一個元素是否在一個集合中。譬如:

  • 網頁爬蟲對URL的去重,避免爬取相同的URL地址
  • 反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信)
  • 緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉
  • 問題:

    可能有人會問,我們直接把這些數據都放到數據庫或者redis之類的緩存中不就行了,查詢時直接匹配不就OK了?

    是的,當這個集合量比較小,你內存又夠大時,是可以這樣做,你可以直接弄個HashSet、HashMap就OK了。但是當這個量以數十億計,內存裝不下,數據庫檢索極慢時該怎么辦。

    4種解決方案:

  • 將所有垃圾郵箱地址存到數據庫,匹配時遍歷
  • 用HashSet存儲所有地址,匹配時接近O(1)的效率查出來
  • 將地址用MD5算法或其他單向映射算法計算后存入HashSet,無論地址多大,保存的只有MD5后的固定位數
  • 布隆過濾器,將所有地址經過多個Hash算法,映射到一個bit數組
  • 4種方案優劣:

  • 方案1,因為是保存完整的地址,占用空間大。一個地址16字節,10億即可達到上百G的內存。而且數據庫的查詢效率也比不上HashSet等數據結構
  • 方案2,雖然HashSet效率逼近O(1),但底層HashMap的擴容因子是0.75,也就是你想存75個數,至少需要一個100的數組,而且還會有不少的沖突。實際上,Hash的存儲效率是0.5左右,存5個數需要10個的空間。算起來占用空間還是挺大的
  • 方案3,因為保存部分信息,所以占用空間小于存儲完整信息,但仍然存在沖突的可能(非垃圾郵箱可能MD5后和某垃圾郵箱一樣,概率低)
  • 方案4,將所有地址經過Hash后映射到同一個bit數組,因為只有一個超大的bit數組,保存所有的映射,占用空間極小,沖突概率高。只要概率在可以接受的范圍,用時間換空間
  • 可以看到,每種方案都有自己的不足之處,所以一定要根據數據規模和業務需求來確定到底使用什么。

    3.BloomFilter 代碼實現

    下面就直接貼代碼,注釋已經寫得很詳細了,而且布隆過濾器的邏輯本身就不復雜,應該還是比較容易理解的…

    class BloomFilter{// 二進制向量(數組)的位數,相當于能存儲1000萬條url左右,誤報率為千萬分之一private static final int BIT_SIZE = 2 << 28 ;// 二進制數組(2^28 byte)private BitSet bits = new BitSet(BIT_SIZE);// 用于生成信息指紋的8個隨機數,最好選取質數(使同一算法產生8個不同hash函數)private static final int[] seeds = new int[]{3, 5, 7, 11, 13, 31, 37, 61}; // 用于存儲具體的8個哈希對象(每個里面包含了不同結果的hash函數)private Hash[] func = new Hash[seeds.length];public BloomFilter(){// 構造BloomFilter時生成這8個隨機哈希對象for(int i = 0; i < seeds.length; i++){func[i] = new Hash(BIT_SIZE, seeds[i]);}}/*** 操作一:向過濾器中添加字符串*/public void addValue(String value) { if(value != null){// 將字符串value哈希為8個或多個整數for(Hash f : func) //然后在這些整數的bit上變為1bits.set(f.hash(value), true); }} /*** 操作二:判斷字符串是否包含在布隆過濾器中*/public boolean contains(String value) { if(value == null) return false; boolean ret = true; // 將要比較的字符串重新以上述方法計算hash值,再與布隆過濾器比對for(Hash f : func)// 注:這里 && ret 很關鍵,若有false了則能保證結果一定是falseret = ret && bits.get(f.hash(value)); return ret; } /*** 隨機哈希值對象(靜態內部類)*/public static class Hash{private int size; // 二進制向量數組大小private int seed; // 隨機數種子public Hash(int cap, int seed){this.size = cap;this.seed = seed;}/*** 計算哈希值(也可以選用別的恰當的哈希函數)*/public int hash(String value){int result = 0;int len = value.length();// 具體Hash算法:加權求和!!!for(int i = 0; i < len; i++){result = seed * result + value.charAt(i);}// 注:取模,得到具體二進制數組下標(result%(size-1))return (size - 1) & result;}} } public class Test {public static void main(String[] args){BloomFilter b = new BloomFilter();b.addValue("www.baidu.com");b.addValue("www.sohu.com");System.out.println(b.contains("www.baidu.com"));System.out.println(b.contains("www.sina.com"));} }

    4.Guava自帶BloomFilter

    除了自己實現BloomFilter,還可以直接使用guava包自帶的布隆過濾器。

    <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>22.0</version> </dependency>

    下面是BloomFilter+redis的一個示例,目的是防止緩存穿透:

    import com.google.common.hash.BloomFilter;// 初始化布隆過濾器 // 1000:期望存入的數據個數,0.001:期望的誤差率 BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf‐8")), 1000, 0.001);//把所有數據存入布隆過濾器 void init(){ for (String key: keys) { bloomFilter.put(key); } }String get(String key) { 1// 從布隆過濾器這一級緩存判斷下key是否存在 Boolean exist = bloomFilter.mightContain(key); if(!exist){ return ""; } // 從緩存中獲取數據 String cacheValue = cache.get(key); // 緩存為空 if (StringUtils.isBlank(cacheValue)) { // 從存儲中獲取 String storageValue = storage.get(key); cache.set(key, storageValue); // 如果存儲數據為空, 需要設置一個過期時間(300秒) if (storageValue == null) { cache.expire(key, 60 * 5); } return storageValue; } else { // 緩存非空 return cacheValue; } }

    總結

    以上是生活随笔為你收集整理的【数据结构】布隆过滤器:BloomFilter原理及Java实现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。