java 布隆過濾器_牛逼哄哄的布隆過濾器,到底有什么用?

65c4cb399aa4e6eb04377d3cbf615f10.png

Java技術棧

www.javastack.cn

打開網站看更多優質文章

作者:CodeBear的園子

www.cnblogs.com/CodeBear/p/10911177.html

本文是站在小白的角度去討論布隆過濾器,如果你是科班出身,或者比較聰明,又或者真正想完全搞懂布隆過濾器的可以移步。

不知道從什么時候開始,本來默默無聞的布隆過濾器一下子名聲大燥,仿佛身在互聯網,做著開發的,無人不知,無人不曉,哪怕對技術不是很關心的小伙伴也聽過它的名號。

我也花了不少時間去研究布隆過濾器,看了不少博客,無奈不是科班出身,又沒有那么聰明的頭腦,又比較懶...經過“放棄,拿起,放棄,拿起”的無限輪回,應該算是了解了布隆過濾器的核心思想,所以想給大家分享下。

布隆過濾器的應用

我們先來看下布隆過濾器的應用場景,讓大家知道神奇的布隆過濾器到底能做什么。

緩存穿透

我們經常會把一部分數據放在Redis等緩存,比如產品詳情。這樣有查詢請求進來,我們可以根據產品Id直接去緩存中取數據,而不用讀取數據庫,這是提升性能最簡單,最普遍,也是最有效的做法。面試常問,緩存三大問題及解決方案!一般的查詢請求流程是這樣的:先查緩存,有緩存的話直接返回,如果緩存中沒有,再去數據庫查詢,然后再把數據庫取出來的數據放入緩存,一切看起來很美好。但是如果現在有大量請求進來,而且都在請求一個不存在的產品Id,會發生什么?既然產品Id都不存在,那么肯定沒有緩存,沒有緩存,那么大量的請求都懟到數據庫,數據庫的壓力一下子就上來了,還有可能把數據庫打死。雖然有很多辦法都可以解決這問題,但是我們的主角是“布隆過濾器”,沒錯,“布隆過濾器”就可以解決(緩解)緩存穿透問題。至于為什么說是“緩解”,看下去你就明白了。

大量數據,判斷給定的是否在其中

現在有大量的數據,而這些數據的大小已經遠遠超出了服務器的內存,現在再給你一個數據,如何判斷給你的數據在不在其中。

如果服務器的內存足夠大,那么用HashMap是一個不錯的解決方案,理論上的時間復雜度可以達到O(1),但是現在數據的大小已經遠遠超出了服務器的內存,所以無法使用HashMap,這個時候就可以使用“布隆過濾器”來解決這個問題。但是還是同樣的,會有一定的“誤判率”。

什么是布隆過濾器

布隆過濾器是一個叫“布隆”的人提出的,它本身是一個很長的二進制向量,既然是二進制的向量,那么顯而易見的,存放的不是0,就是1。

現在我們新建一個長度為16的布隆過濾器,默認值都是0,就像下面這樣:f05cc98c5d548751250c2f5413298f62.png

現在需要添加一個數據:

我們通過某種計算方式,比如Hash1,計算出了Hash1(數據)=5,我們就把下標為5的格子改成1,就像下面這樣:

4bc633fb94b7e738e50b9ff02af83c66.png

我們又通過某種計算方式,比如Hash2,計算出了Hash2(數據)=9,我們就把下標為9的格子改成1,就像下面這樣:df10c5c0b5787767aaf64fda6bfc73c9.png

還是通過某種計算方式,比如Hash3,計算出了Hash3(數據)=2,我們就把下標為2的格子改成1,就像下面這樣:58e0a9b64dcfbfa30d8f73a0a9467ad1.png

這樣,剛才添加的數據就占據了布隆過濾器“5”,“9”,“2”三個格子。

可以看出,僅僅從布隆過濾器本身而言,根本沒有存放完整的數據,只是運用一系列隨機映射函數計算出位置,然后填充二進制向量。

這有什么用呢?比如現在再給你一個數據,你要判斷這個數據是否重復,你怎么做?

你只需利用上面的三種固定的計算方式,計算出這個數據占據哪些格子,然后看看這些格子里面放置的是否都是1,如果有一個格子不為1,那么就代表這個數字不在其中。

這很好理解吧,比如現在又給你了剛才你添加進去的數據,你通過三種固定的計算方式,算出的結果肯定和上面的是一模一樣的,也是占據了布隆過濾器“5”,“9”,“2”三個格子。

但是有一個問題需要注意,如果這些格子里面放置的都是1,不一定代表給定的數據一定重復,也許其他數據經過三種固定的計算方式算出來的結果也是相同的。這也很好理解吧,比如我們需要判斷對象是否相等,是不可以僅僅判斷他們的哈希值是否相等的。

也就是說布隆過濾器只能判斷數據是否一定不存在,而無法判斷數據是否一定存在。

按理來說,介紹完了新增、查詢的流程,就要介紹刪除的流程了,但是很遺憾的是布隆過濾器是很難做到刪除數據的,為什么?你想想,比如你要刪除剛才給你的數據,你把“5”,“9”,“2”三個格子都改成了0,但是可能其他的數據也映射到了“5”,“9”,“2”三個格子啊,這不就亂套了嗎?

相信經過我這么一介紹,大家對布隆過濾器應該有一個淺顯的認識了,至少你應該清楚布隆過濾器的優缺點了:

  • 優點:由于存放的不是完整的數據,所以占用的內存很少,而且新增,查詢速度夠快;

  • 缺點:隨著數據的增加,誤判率隨之增加;無法做到刪除數據;只能判斷數據是否一定不存在,而無法判斷數據是否一定存在。

可以看到,布隆過濾器的優點和缺點一樣明顯。

在上文中,我舉的例子二進制向量長度為16,由三個隨機映射函數計算位置,在實際開發中,如果你要添加大量的數據,僅僅16位是遠遠不夠的,為了讓誤判率降低,我們還可以用更多的隨機映射函數、更長的二進制向量去計算位置。

guava實現布隆過濾器

現在相信你對布隆過濾器應該有一個比較感性的認識了,布隆過濾器核心思想其實并不難,難的在于如何設計隨機映射函數,到底映射幾次,二進制向量的長度設置為多少比較好,這可能就不是一般的開發可以駕馭的了。

好在Google大佬給我們提供了開箱即用的組件,來幫助我們實現布隆過濾器,現在就讓我們看看怎么Google大佬送給我們的“禮物”吧。

首先在pom引入“禮物”:

<dependency>
??<groupId>com.google.guavagroupId>
??<artifactId>guavaartifactId>
??<version>19.0version>
dependency>

然后就可以測試啦:

private?static?int?size = 1000000;//預計要插入多少數據private?static?double?fpp = 0.01;//期望的誤判率private?static?BloomFilter bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);public?static?void?main(String[] args) {//插入數據for?(int?i = 0; i < 1000000; i++) {
????bloomFilter.put(i);
??}int?count = 0;for?(int?i = 1000000; i < 2000000; i++) {if?(bloomFilter.mightContain(i)) {
??????count++;
??????System.out.println(i + "誤判了");
????}
??}
??System.out.println("總共的誤判數:"?+ count);
}

代碼簡單分析:

我們定義了一個布隆過濾器,有兩個重要的參數,分別是 我們預計要插入多少數據,我們所期望的誤判率,誤判率不能為0。

我向布隆過濾器插入了0-1000000,然后用1000000-2000000來測試誤判率。

運行結果:

1999501誤判了1999567誤判了1999640誤判了1999697誤判了1999827誤判了1999942誤判了
總共的誤判數:10314

現在總共有100萬數據是不存在的,誤判了10314次,我們計算下誤判率:

3a8fdd29e149deb9b8341f0efc6b5fb3.png

和我們定義的期望誤判率0.01相差無幾。

redis實現布隆過濾器

上面使用guava實現布隆過濾器是把數據放在本地內存中,無法實現布隆過濾器的共享,我們還可以把數據放在redis中,用 redis來實現布隆過濾器,我們要使用的數據結構是bitmap,你可能會有疑問,redis支持五種數據結構:String,List,Hash,Set,ZSet,沒有bitmap呀。沒錯,實際上bitmap的本質還是String。

可能有小伙伴會說,納尼,布隆過濾器還沒介紹完,怎么又出來一個bitmap,沒事,你可以把bitmap就理解為一個二進制向量。

要用redis來實現布隆過濾器,我們需要自己設計映射函數,自己度量二進制向量的長度,這對我來說,無疑是一個不可能完成的任務,只能借助搜索引擎,下面直接放出代碼把。

public?class?RedisMain?{static?final int?expectedInsertions = 100;//要插入多少數據static?final double?fpp = 0.01;//期望的誤判率//bit數組長度private?static?long?numBits;//hash函數數量private?static?int?numHashFunctions;static?{
????????numBits = optimalNumOfBits(expectedInsertions, fpp);
????????numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
????}public?static?void?main(String[] args)?{
????????Jedis jedis = new?Jedis("192.168.0.109", 6379);for?(int?i = 0; i < 100; i++) {long[] indexs = getIndexs(String.valueOf(i));for?(long?index : indexs) {
????????????????jedis.setbit("codebear:bloom", index, true);
????????????}
????????}for?(int?i = 0; i < 100; i++) {long[] indexs = getIndexs(String.valueOf(i));for?(long?index : indexs) {
????????????????Boolean isContain = jedis.getbit("codebear:bloom", index);if?(!isContain) {
????????????????????System.out.println(i + "肯定沒有重復");
????????????????}
????????????}
????????????System.out.println(i + "可能重復");
????????}
????}/**
?????* 根據key獲取bitmap下標
?????*/private?static?long[] getIndexs(String key) {long?hash1 = hash(key);long?hash2 = hash1 >>> 16;long[] result = new?long[numHashFunctions];for?(int?i = 0; i < numHashFunctions; i++) {long?combinedHash = hash1 + i * hash2;if?(combinedHash < 0) {
????????????????combinedHash = ~combinedHash;
????????????}
????????????result[i] = combinedHash % numBits;
????????}return?result;
????}private?static?long?hash(String key)?{
????????Charset charset = Charset.forName("UTF-8");return?Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
????}//計算hash函數個數private?static?int?optimalNumOfHashFunctions(long?n, long?m)?{return?Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
????}//計算bit數組長度private?static?long?optimalNumOfBits(long?n, double?p)?{if?(p == 0) {
????????????p = Double.MIN_VALUE;
????????}return?(long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
????}
}

運行結果:

88可能重復
89可能重復
90可能重復
91可能重復
92可能重復
93可能重復
94可能重復
95可能重復
96可能重復
97可能重復
98可能重復
99可能重復
本篇博客到這里就結束了,謝謝大家。寫作不易,堅持更難,如大家喜歡就幫忙推送給其他人!最近熱文:1、Tomcat 又爆出高危漏洞!8.5 ~ 10 中招…2、Spring Boot 干掉了 Maven 擁抱 Gradle!3、打破你的認知,數字除以0一定會崩潰嗎?4、寫了個全局變量的bug,被同事們打臉!5、Java 14 祭出神器,Lombok 被干掉了?6、為什么 Redis 單線程能達到百萬+QPS?7、Spring Boot 2.3 優雅關閉新姿勢,真香!8、玩大發了,Tomcat 8.5 升級有坑…9、我天!xx.equals(null) 是什么騷操作??10、Spring Boot 2.3.1 發布, 10 個新特性!掃碼關注Java技術棧公眾號干貨。

34f063b0c3ddc9c9cc40518300a019d0.png

點擊「」獲取面試題大全~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446307.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446307.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446307.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

密碼學專題 序列號文件

序列號文件是ca指令簽發證書的時候的依據文件之一&#xff0c;它從該文件讀取當前簽發的證書的序列號并將序列號文件中的序列號加1&#xff0c;這樣&#xff0c;就可以確保證書的序論號是遞增的&#xff0c;不會重復。序列號文件也是一個文本文件&#xff0c;里面僅僅簡單包含了…

Java web后端6 java Bean EL表達式

EL表達式和JSTL概述 java Bean規范 java中成員變量使用類Integer private Integer count; java Bean的創建 創建java Bean: BookTest.java package com.example.elandjstl.bean;public class BookTest {//java中成員變量使用類Integerprivate Integer count;private Boolean…

python根須系統斜杠_深入淺出Python中的os模塊

「Author&#xff1a;Runsen」當初學Python的時候&#xff0c;把一些標準庫和第三方開源庫學的七零八落&#xff0c;不成系統&#xff0c;正好趁這個機會來系統的整理一下&#xff0c;先從Python常用的標準庫os開始吧。osOS模塊簡單的來說它是一個Python的系統編程的操作模塊&a…

密碼學專題 隨機數文件

無論使用OpenSSL的指令還是其API&#xff0c;隨機數文件都是會經常碰到的一個概念。大部分密碼算法的安全性都跟隨機數的好壞相關&#xff0c;所以一個成功的密碼學應用軟件&#xff0c;對隨機數的處理是不能隨便的。OpenSSL雖然沒有提供很完美的隨機數生成程序&#xff0c;但是…

Java web后端7JSTL

概括 下載jstl的jar包 官網&#xff1a;https://mvnrepository.com/ 網址1&#xff1a;https://search.maven.org/ 在pomxml中插入依賴&#xff1a; <dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1…

python輸入程序_python程序的輸入輸出(acm的幾個小程序)

#!/usr/bin/env python#codingutf-8 a[]for x inraw_input().split(): a.append(int(x))print sum(a) 下面的代碼只有一行&#xff0c;&#xff0c;可惜不是我想出來的&#xff01;&#xff01;&#xff01;&#xff01;&#xff1a; print sum(int(x) for x in raw_input().sp…

密碼學專題 口令輸入的方式

雖然口令的安全性很值得擔憂&#xff0c;但是口令在OpenSSL中是經常使用的&#xff0c;這是沒有辦法替代的一種簡易的保護數據的方法。OpenSSL中使用口令的地方很多&#xff0c;比如密鑰的加密和解密&#xff0c;等等。OpenSSL的指令提供了多種靈活的口令輸入方法&#xff0c;但…

Python學習14 模塊和包

模塊 公共類、函數都可以放在獨立的文件中&#xff0c;這樣其他多個程序都可以使用&#xff0c;而不必把這些公共性的類、函數等在每個程序中復制一份&#xff0c;這樣獨立的文件就叫做模塊&#xff0c;它們的擴展名為.py 標準庫中的模塊 使用help查看模塊 代碼&#xff1a; …

python語句分為_python以什么劃分語句塊

語句塊是在條件為真&#xff08;條件語句&#xff09;時執行或者執行多次&#xff08;循環語句&#xff09;的一組語句&#xff1b;在代碼前放置空格來縮進語句即可創建語句塊&#xff0c;語句塊中的每行必須是同樣的縮進量&#xff1b;&#xff08;推薦學習&#xff1a;Python…

Python學習15 正則表達式1

網址 正則表達式測試網址&#xff1a;https://regex101.com/ 概述 正則表達式&#xff1a; 正則表達式(Regular Expression)是一種文本模式&#xff0c;包括普通字符&#xff08;例如&#xff0c;a 到 z 之間的字母&#xff09;和特殊字符&#xff08;稱為"元字符"…

STL源碼剖析 空間配置器 查漏補缺

ptrdiff_t含義 減去兩個指針的結果的帶符號整數類型ptrdiff_t (Type support) - C 中文開發手冊 - 開發者手冊 - 云社區 - 騰訊云 std::set_new_handler&#xff08;&#xff09;函數的理解 關于set_new_handler的理解_wck0617-CSDN博客new分配內存的時候 如果分配的空間不…

python每天定時9點執行_python定時器每天訂時執行的實例方法

python定時器,實現每天凌晨3點執行的方法 如下所示&#xff1a;Created on 2018-4-20 例子:每天凌晨3點執行func方法import datetime import threading def func(): print("haha") #如果需要循環調用&#xff0c;就要添加以下方法 timer threading.Timer(86400, fun…

Python學習16 正則表達式2 re模塊

re 模塊 re 模塊&#xff1a; Python的 re 模塊實現了正則表達式處理的功能。 導入re模塊后&#xff0c;使用findall、search函數可以進行匹配 查找&#xff1a;match和search 多個匹配上的&#xff0c;也只會返回第一個匹配上的 re.match()&#xff1a; 需要特別注意的是&…

STL源碼剖析 內存基本處理工具 初始化空間的五個函數

初始化空間的五個函數構造函數 construct()析構函數 destroy()剩余三個底層函數 和 高層函數之間的對應關系如下uninitialized_copy() 對應 copy()uninitialized_fill() 對應 fill()uninitialized_fill_n() 對應 fill_n()使用<memory>使用上述三個底層函數 uninitiali…

單基因gsea_篩到5分的核心基因以后你可以怎么做?

這一次我們從一些已經發表的文章拆解&#xff0c;我們來看看&#xff0c;你找到了一個核心基因以后&#xff0c;你可以怎么做呢&#xff1f;我們就不說那么多廢話了&#xff0c;直接用幾篇文章的解讀來帶著大家領會一下如何去進行下一步的分析。Case1&#xff1a;預后標志物免疫…

安卓 原生okhttp使用get與post獲取網絡數據

網址 https://square.github.io/okhttp/ 配置 依賴 在module的build.gradle中&#xff1a; implementation com.squareup.okhttp3:okhttp:3.14.7implementation com.squareup.okio:okio:1.17.5AndroidManifest.xml <uses-permission android:name"android.permissi…

STL源碼剖析 迭代器的概念和traits編程技法

迭代器&#xff1a;依序巡防某個聚合物(容器)所含的各個元素&#xff0c;但是不需要暴露這個聚合物的內部表述方式核心思想&#xff1a;將容器和算法分開&#xff0c;彼此獨立設計容器和算法的泛型化&#xff0c;均可以使用模板&#xff0c;使用迭代器連接容器和算法例子 templ…

.sql文件如何執行_干貨|一條SQL查詢語句是如何執行的

作者&#xff1a;wanber鏈接&#xff1a;https://blog.nowcoder.net/n/9e120e8f1314466bb44fe706b283dc20

STL源碼剖析 5中迭代器型別

最常使用的5種迭代器的型別 為 value_type、difference_type、pointer、reference、iterator_category。如果想要自己開發的容器和STL進行適配&#xff0c;就需要定義上述5種類型 iteraor_traits 必須針對傳入的型別為 pointer 或者 pointer-to-const設計偏特化版本 template &…

Python學習16 正則表達式3 練習題

用戶名匹配 1.用戶名匹配&#xff1a;由數字、大小寫字母、下劃線_、中橫線-組成&#xff0c;長度為6-12位&#xff0c;不能以數字開頭。 import re usernameab578_-SDF resultre.search(^[a-zA-Z_-][0-9a-zA-Z_-]{5,12}$,username) print(result)郵箱 2.驗證輸入的郵箱&…