bloomfilter的java实现,BloomFilter(布隆过滤器)原理及实战详解
什么是 BloomFilter(布隆過濾器)
布隆過濾器(英語:Bloom Filter)是 1970 年由布隆提出的。它實(shí)際上是一個(gè)很長的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。主要用于判斷一個(gè)元素是否在一個(gè)集合中。通常我們會(huì)遇到很多要判斷一個(gè)元素是否在某個(gè)集合中的業(yè)務(wù)場景,這個(gè)時(shí)候往往我們都是采用 Hashmap,Set 或者其他集合將數(shù)據(jù)保存起來,然后進(jìn)行對比判斷,但是如果元素很多的情況,我們?nèi)绻捎眠@種方式就會(huì)非常浪費(fèi)空間。這個(gè)時(shí)候我們就需要 BloomFilter 來幫助我們了。
BloomFilter 原理
BloomFilter 是由一個(gè)固定大小的二進(jìn)制向量或者位圖(bitmap)和一系列(通常好幾個(gè))映射函數(shù)組成的。布隆過濾器的原理是,當(dāng)一個(gè)變量被加入集合時(shí),通過 K 個(gè)映射函數(shù)將這個(gè)變量映射成位圖中的 K 個(gè)點(diǎn),把它們置為 1。查詢某個(gè)變量的時(shí)候我們只要看看這些點(diǎn)是不是都是 1 就可以大概率知道集合中有沒有它了,如果這些點(diǎn)有任何一個(gè) 0,則被查詢變量一定不在;如果都是 1,則被查詢變量很可能在。注意,這里是可能存在,不一定一定存在!這就是布隆過濾器的基本思想。
如下圖所示,字符串 “ziyou” 在經(jīng)過四個(gè)映射函數(shù)操作后在位圖上有四個(gè)點(diǎn)被設(shè)置成了 1。當(dāng)我們需要判斷 “ziyou” 字符串是否存在的時(shí)候只要在一次對字符串進(jìn)行映射函數(shù)的操作,得到四個(gè) 1 就說明 “ziyou” 是可能存在的。
為什么說是可能存在,而不是一定存在呢?那是因?yàn)橛成浜瘮?shù)本身就是散列函數(shù),散列函數(shù)是會(huì)有碰撞的,意思也就是說會(huì)存在一個(gè)字符串可能是 “ziyou01” 經(jīng)過相同的四個(gè)映射函數(shù)運(yùn)算得到的四個(gè)點(diǎn)跟 “ziyou” 是一樣的,這種情況下我們就說出現(xiàn)了誤算。另外還有可能這四個(gè)點(diǎn)位上的 1 是四個(gè)不同的變量經(jīng)過運(yùn)算后得到的,這也不能證明字符串 “ziyou” 是一定存在的,如下圖框出來的 1 也可能是字符串“張三”計(jì)算得到,同理其他幾個(gè)位置的 1 也可以是其他字符串計(jì)算得到。
1.2 特性
所以通過上面的例子我們就可以明確
一個(gè)元素如果判斷結(jié)果為存在的時(shí)候元素不一定存在,但是判斷結(jié)果為不存在的時(shí)候則一定不存在。
布隆過濾器可以添加元素,但是不能刪除元素。因?yàn)閯h掉元素會(huì)導(dǎo)致誤判率增加。
02、使用場景
2.1、網(wǎng)頁 URL 去重
我們在使用網(wǎng)頁爬蟲的時(shí)候(爬蟲需謹(jǐn)慎),往往需要記錄哪些 URL 是已經(jīng)爬取過的,哪些還是沒有爬取過,這個(gè)時(shí)候我們就可以采用 BloomFilter 來對已經(jīng)爬取過的 URL 進(jìn)行存儲(chǔ),這樣在進(jìn)行下一次爬取的時(shí)候就可以判斷出這個(gè) URL 是否爬取過。
2.2、黑白名單存儲(chǔ)
工作中經(jīng)常會(huì)有一個(gè)特性針對不同的設(shè)備或者用戶有不同的處理方式,這個(gè)時(shí)候可能會(huì)有白名單或者黑名單存在,所以根據(jù) BloomFilter 過濾器的特性,我們也可以用它來存在這些數(shù)據(jù),雖然有一定的誤算率,但是在一定程度上還是可以很好的解決這個(gè)問題的。
2.3、小結(jié)
除了上面說的兩種場景,其實(shí)還有很多場景,比如熱點(diǎn)數(shù)據(jù)訪問,垃圾郵件過濾等等,其實(shí)這些場景的統(tǒng)一特性就是要判斷某個(gè)元素是否在某個(gè)集合中,原理都是一樣的。
03、代碼實(shí)踐
3.1、自己實(shí)現(xiàn)
package com.test.pkg;
import java.util.BitSet;
/**
*
* Function:
* Author:@author ziyou
* Date:2019-10-23 23:21
* Desc:無
*/
public class BloomFilterTest {
/**
* 初始化布隆過濾器的 bitmap 大小
*/
private static final int DEFAULT_SIZE = 2 << 24;
/**
* 為了降低錯(cuò)誤率,這里選取一些數(shù)字作為基準(zhǔn)數(shù)
*/
private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};
/**
* 設(shè)置 bitmap
*/
private static BitSet bitset = new BitSet(DEFAULT_SIZE);
/**
* 設(shè)置 hash 函數(shù)數(shù)量
*/
private static HashFunction[] functions = new HashFunction[seeds.length];
/**
* 添加數(shù)據(jù)
*
* @param value 需求加入的值
*/
public static void put(String value) {
if (value != null) {
for (HashFunction f : functions) {
//計(jì)算 hash 值并修改 bitmap 中相應(yīng)位置為 true
bitset.set(f.hash(value), true);
}
}
}
/**
* 判斷相應(yīng)元素是否存在
*
* @param value 需要判斷的元素
* @return 結(jié)果
*/
public static boolean check(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (HashFunction f : functions) {
ret = bitset.get(f.hash(value));
//一個(gè) hash 函數(shù)返回 false 則跳出循環(huán)
if (!ret) {
break;
}
}
return ret;
}
public static void main(String[] args) {
String value = "test";
for (int i = 0; i < seeds.length; i++) {
functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);
}
put(value);
System.out.println(check("value"));
}
}
class HashFunction {
private int size;
private int seed;
public HashFunction(int size, int seed) {
this.size = size;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i);
}
int r = (size - 1) & result;
return (size - 1) & result;
}
}
上面我們自己寫了一個(gè)簡單的 BloomFilter ,通過 put 方法錄入數(shù)據(jù),通過 check 方法判斷元素是否存在,基本能實(shí)現(xiàn)功能,代碼中注釋也寫的很清楚,但是自己實(shí)現(xiàn)必定效率不高,所以下面我們看下業(yè)內(nèi)大佬幫我們已經(jīng)實(shí)現(xiàn)好的 BloomFilter。
2.4、Guava 中的 BloomFilter
package com.test.pkg;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
/**
*
* Function:
* Author:@author ziyou
* Date:2019-10-24 00:17
* Desc:無
*/
public class BloomFilterTest02 {
public static void main(String[] args) {
BloomFilter bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 100000, 0.01);
for (int i = 0; i < 100000; i++) {
bloomFilter.put(i);
}
System.out.println(bloomFilter.mightContain(1));
System.out.println(bloomFilter.mightContain(2));
System.out.println(bloomFilter.mightContain(3));
System.out.println(bloomFilter.mightContain(100001));
}
}
Guava 中已經(jīng)幫我們實(shí)現(xiàn)好了 BloomFilter 的代碼,我們只需要在使用的地方調(diào)用就好。
這里我們簡單解釋一下構(gòu)造方法中的后面兩個(gè)參數(shù),一個(gè)是預(yù)計(jì)包含的數(shù)據(jù)量,一個(gè)是允許的誤差值。代碼中會(huì)根據(jù)我們填入的這兩個(gè)值,自動(dòng)幫我們計(jì)算出數(shù)組的大小,以及需要的散列函數(shù)個(gè)數(shù),如下圖。更多詳細(xì)的內(nèi)容,讀者可以自行去查看源碼,我們這里就不介紹了。
04、總結(jié)
這篇文章給大家介紹了 BloomFilter,一個(gè)用來判斷元素是否存在與某個(gè)集合的高效方法,可以在我們?nèi)粘5墓ぷ髦羞\(yùn)用起來,結(jié)合日常工作的場景,可以進(jìn)行選擇。
原文發(fā)于:《Java極客技術(shù)》公眾號,作者:ziyou
關(guān)注公眾號:程序新視界,一個(gè)讓你軟實(shí)力、硬技術(shù)同步提升的平臺(tái)
除非注明,否則均為程序新視界原創(chuàng)文章,轉(zhuǎn)載必須以鏈接形式標(biāo)明本文鏈接
總結(jié)
以上是生活随笔為你收集整理的bloomfilter的java实现,BloomFilter(布隆过滤器)原理及实战详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ping cat.flag.php,关于
- 下一篇: oracle查看表的命令,Oracle常