用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的布隆過(guò)濾器
設(shè)計(jì)初衷
在實(shí)際開(kāi)發(fā)中,會(huì)遇到很多要判斷一個(gè)元素是否在某個(gè)集合中的業(yè)務(wù)場(chǎng)景,類似于垃圾郵件的識(shí)別,惡意ip地址的訪問(wèn),緩存穿透等情況。類似于緩存穿透這種情況,有許多的解決方法,如:redis存儲(chǔ)null值等,而對(duì)于垃圾郵件的識(shí)別,惡意ip地址的訪問(wèn),我們也可以直接用 HashMap 去存儲(chǔ)惡意ip地址以及垃圾郵件,然后每次訪問(wèn)時(shí)去檢索一下對(duì)應(yīng)集合中是否有相同數(shù)據(jù)。
但是對(duì)于大數(shù)據(jù)量的項(xiàng)目,如,垃圾郵件出現(xiàn)有十幾二十萬(wàn),惡意ip地址出現(xiàn)有上百萬(wàn),或者從幾十億電話中檢索出指定的電話是否在等操作,那么這十幾億的數(shù)據(jù)就會(huì)占據(jù)大幾G的空間,這個(gè)時(shí)候就可以考慮一下布隆過(guò)濾器了。
?網(wǎng)頁(yè)URL的去重,垃圾郵件的判別,集合重復(fù)元素的判別,查詢加速(比如基于key-value的存儲(chǔ)系統(tǒng))、數(shù)據(jù)庫(kù)防止查詢擊穿, 使用BloomFilter來(lái)減少不存在的行或列的磁盤查找
布隆過(guò)濾器定義
布隆過(guò)濾器(Bloom Filter)是1970年由布隆提出的。它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。
由一個(gè)初始值為零的bit數(shù)組和多個(gè)哈希函數(shù)構(gòu)成,用來(lái)快速判斷集合中是否存在某個(gè)元素。
布隆過(guò)濾器可以用于查詢一個(gè)元素是否存在于一個(gè)集合當(dāng)中,查詢結(jié)果為以下二者之一:
- 這個(gè)元素可能存在于這個(gè)集合當(dāng)中。
- 這個(gè)元素一定不存在于這個(gè)集合當(dāng)中。
進(jìn)行數(shù)據(jù)插入時(shí):使用多個(gè)hash函數(shù)對(duì)key進(jìn)行hash運(yùn)算得到多個(gè)整數(shù)索引值,對(duì)位數(shù)組長(zhǎng)度進(jìn)行取模運(yùn)算得到多個(gè)位置,每個(gè)hash函數(shù)都會(huì)得到一個(gè)不同的位置,將這幾個(gè)位置都置1就完成了add操作。
進(jìn)行數(shù)據(jù)查詢時(shí):將這個(gè)key的多個(gè)位置上的值取出來(lái),只要有其中一位是零就表示這個(gè)key不存在,但如果都是1,則不一定存在對(duì)應(yīng)的key。(也就是有,不一定有,無(wú),就一定無(wú))
java實(shí)現(xiàn)
基于上面理解介紹 ,我們現(xiàn)在基于java手?jǐn)]一個(gè)簡(jiǎn)單布隆過(guò)濾器
- bitSize:位圖的大小,即位圖中的位數(shù)。
- bits:位圖對(duì)象,用于存儲(chǔ)元素的映射結(jié)果。
- seeds:用于哈希函數(shù)的種子數(shù)組。
- hashIterations:哈希函數(shù)的迭代次數(shù)。
class BloomFilter { private int bitSize; private BitSet bits; private int[] seeds; private int hashIterations; /** * @param size 預(yù)計(jì)元素?cái)?shù)量 * @param falsePositive 期望誤判率 */ public BloomFilter(int size, double falsePositive) { this.bitSize = (int) Math.ceil((size * Math.log(falsePositive)) / Math.log(1.0 / (Math.pow(2.0, Math.log(2.0))))); this.bits = new BitSet(bitSize); this.hashIterations = (int) Math.round(Math.log(2.0) * bitSize / size); this.seeds = new int[hashIterations]; for (int i = 0; i < hashIterations; i++) { seeds[i] = i + 1; } } /** * 添加一個(gè)元素 * * @param element 元素 */ public void add(String element) { for (int seed : seeds) { int hash = MurmurHash.hash(element.getBytes(), seed); bits.set(Math.abs(hash % bitSize), true); } } /** * 判斷一個(gè)元素是否存在 * * @param element 元素 * @return 是否存在 */ public boolean contains(String element) { for (int seed : seeds) { int hash = MurmurHash.hash(element.getBytes(), seed); if (!bits.get(Math.abs(hash % bitSize))) { return false; } } return true; } /** * MurmurHash算法 */ static class MurmurHash { public static int hash(byte[] data, int seed) { int m = 0x5bd1e995; int r = 24; int h = seed ^ data.length; int len = data.length; int pos = 0; while (len >= 4) { int k = data[pos] & 0xff; k |= (data[pos + 1] & 0xff) << 8; k |= (data[pos + 2] & 0xff) << 16; k |= (data[pos + 3] & 0xff) << 24; k *= m; k ^= k >>> r; k *= m; h *= m; h ^= k; pos += 4; len -= 4; } switch (len) { case 3: h ^= (data[pos + 2] & 0xff) << 16; case 2: h ^= (data[pos + 1] & 0xff) << 8; case 1: h ^= data[pos] & 0xff; h *= m; } h ^= h >>> 13; h *= m; h ^= h >>> 15; return h; } } }
BloomFilter 類表示布隆過(guò)濾器,提供了 add 和 contains 方法用于添加元素和判斷元素是否存在。
在構(gòu)造函數(shù)中,根據(jù)預(yù)計(jì)元素?cái)?shù)量和期望誤判率計(jì)算出位數(shù)組的大小、哈希函數(shù)個(gè)數(shù)和哈希種子。
添加元素時(shí),使用多個(gè)哈希函數(shù)對(duì)元素進(jìn)行哈希,并將對(duì)應(yīng)的位設(shè)置為 1;判斷元素是否存在時(shí),同樣使用多個(gè)哈希函數(shù)對(duì)元素進(jìn)行哈希,并檢查對(duì)應(yīng)的位是否都為 1。
注意,上述代碼中的哈希函數(shù)使用了 MurmurHash 算法,該算法的性能比較高,適合用于布隆過(guò)濾器中。同時(shí),布隆過(guò)濾器的誤判率隨著元素?cái)?shù)量的增加而增加,因此在實(shí)際使用中需要根據(jù)誤判率和元素?cái)?shù)量的情況來(lái)選擇合適的參數(shù)。
測(cè)試
public class test { public static void main(String[] args) { BloomFilter bloomFilter = new BloomFilter(1000,0.01); bloomFilter.add("xyz"); boolean xyz = bloomFilter.contains("xyz"); System.out.println("xyz查詢結(jié)果:"+xyz); boolean xyzk = bloomFilter.contains("xyzk"); System.out.println("xyz查詢結(jié)果:"+xyzk); } }
測(cè)試結(jié)果:
xyz查詢結(jié)果:true
xyz查詢結(jié)果:false
到此這篇關(guān)于用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的布隆過(guò)濾器的文章就介紹到這了,更多相關(guān)Java實(shí)現(xiàn)布隆過(guò)濾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring @Primary作用和實(shí)現(xiàn)原理詳解
今天分享一下Spring中的@Primary注解,Primary的意思是主要的,我們?cè)谑褂胹pring的時(shí)候,難免會(huì)定義多個(gè)類型相同的bean,這時(shí)候如果不采取一些方法,那么是無(wú)法正常使用bean的,所以本就給大家介紹Spring @Primary的作用和實(shí)現(xiàn)原理2023-07-07java Nio使用NioSocket客戶端與服務(wù)端交互實(shí)現(xiàn)方式
這篇文章主要介紹了java Nio使用 NioSocket 客戶端與服務(wù)端交互實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法
這篇文章主要介紹了詳解MyBatis-Plus updateById方法更新不了空字符串/null解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09SpringBoot2實(shí)現(xiàn)MessageQueue消息隊(duì)列
本文主要介紹了 SpringBoot2實(shí)現(xiàn)MessageQueue消息隊(duì)列,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04linux系統(tǒng)下java項(xiàng)目在后臺(tái)啟動(dòng)的4種方式總結(jié)
Linux是集多種功能于一身的操作系統(tǒng),它可以讓用戶查看和管理當(dāng)下正在運(yùn)行的進(jìn)程,包括Java程序,這篇文章主要給大家總結(jié)介紹了關(guān)于linux系統(tǒng)下java項(xiàng)目在后臺(tái)啟動(dòng)的4種方式,需要的朋友可以參考下2023-10-10SpringCloud項(xiàng)目中Feign組件添加請(qǐng)求頭所遇到的坑及解決
這篇文章主要介紹了SpringCloud項(xiàng)目中Feign組件添加請(qǐng)求頭所遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04kafka springBoot配置的實(shí)現(xiàn)
本文主要介紹了kafka springBoot配置的實(shí)現(xiàn),通過(guò)詳細(xì)解析Spring Boot for Apache Kafka的配置選項(xiàng),以及如何優(yōu)化Kafka生產(chǎn)者和消費(fèi)者的屬性設(shè)置,感興趣的可以了解一下2023-11-11Automapper實(shí)現(xiàn)自動(dòng)映射的實(shí)例代碼
這篇文章主要介紹了Automapper實(shí)現(xiàn)自動(dòng)映射的實(shí)例代碼,需要的朋友可以參考下2017-09-09