布隆过滤器——Bloom Filter

本文介绍了布隆过滤器的基本原理及其在数据去重、URL过滤和用户行为分析中的应用。详细阐述了如何利用布隆过滤器减少网络爬虫重复抓取、过滤重复URL及过滤用户重复访问,同时提供了长整型元素布隆过滤器的完整实现。随着海量数据时代的到来,布隆过滤器在实际应用中展现出高效的空间效率和快速的查询速度,成为解决数据重复问题的重要工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:http://imtinx.iteye.com/blog/1290636


谷歌的数学之美系列曾经提到过一种数据结构叫做bloomfilter,翻译成中文就是布隆过滤,文中使用布隆过滤器来过滤黑名单。后来我在毕业设计中也用到了它来过滤重复的URL,避免网络爬虫重复抓取。再后来在单位又一次的用到了bloomfilter来过滤用户的重复访问。随着海量数据时代的到来,布隆过滤器应用的场景越来越多。

布隆过滤器(Bloom Filter)是1970年由Burton Howard Bloom提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0。


 

为了表达S={X1, X2,…,Xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素X,第i个哈希函数映射的位置Hi(X)就会被置为1(1≤i≤k)。注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一个位置(从左边数第五位)。  

 


 


在判断Y是否属于这个集合时,我们对Y应用k次哈希函数,如果所有Hi(Y)的位置都是1(1≤i≤k),那么我们就认为Y是集合中的元素,否则就认为Y不是集合中的元素。下图中Y1就不是集合中的元素。Y2或者属于这个集合,或者刚好是一个误判。


应用布隆过滤器时,只需要由用户决定要容纳的元素数n和希望的误判率p。然后通过以下公式:


计算出位数组的长度m,然后通过m,n计算出哈希函数的个数k。


 

隆过滤器的优劣主要与哈希函数的质量相关,而且哈希函数之间的相关度越小越好,每个哈希函数本身的计算过程不要太复杂,不然会影响效率。理想情况下是取k个完全不相关的哈希函数,在不是很严格情况下,也可以通过一个哈希函数的参数变化产生k个不同的哈希函数,比如将i1ik)作为参数参与哈希函数的计算。

不同的应用场景,哈希函数的设计方法不同,没有通用的规律可循。在网络爬虫的设计中,我才用了MD5算法最为基础来构造哈希函数:

for (int i = 0; i < funNum; i++){ //输入URL地址拼接上Hash函数的编号 String input = url+i.toString(); //散列值取MD5摘要的后64位与比特向量大小的的余数 hash =(long)Md5(input).getLast64bit() % (long)bitSetSize;}

 

在过滤用户时,由于用户ID是一个long型数据,因此用随机数函数random()效率更高。下面是long类型bloomfilter的完整实现。

 

import java.io.Serializable;import java.util.BitSet;import java.util.Random;/** * Long类型元素的布隆过滤器 */public class BloomFilter implements Serializable { private static final long serialVersionUID = 1L; public static final int ELEM_NUM = 1000; // 欲容纳的元素个数 public static final double PERCENTAGE = 0.001; // 希望的误差率 private int hashNum; // hash函数的数量 private int size; // 位向量的長度 private BitSet bitVecter; // 位向量 public BloomFilter() { size = (int) Math.abs(ELEM_NUM * Math.log(PERCENTAGE) / (Math.log(2) * Math.log(2))) + 1; hashNum = (int) (Math.log(2) * ((double) size / ELEM_NUM)); bitVecter = new BitSet(size); } /** * 查找元素是否在集合中 */ public boolean search(Long elem) { boolean flag = true; int temp; Random random = new Random(elem); for (int i = 0; i < hashNum; i++) { temp = random.nextInt(size); if (!bitVecter.get(temp)) {// 元素不在集合中 bitVecter.set(temp); flag = false; } } return flag; } /** * 获取位向量的长度 */ public int size() { return bitVecter.size(); }

 

public int getHashNum() { return hashNum; } public void setHashNum(int hashNum) { this.hashNum = hashNum; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public BitSet getBitVecter() { return bitVecter; } public void setBitVecter(BitSet bitVecter) { this.bitVecter = bitVecter; }}

### 布隆过滤器在解决缓存穿透问题中的应用 布隆过滤器Bloom Filter)作为一种高效的空间节省型数据结构,被广泛应用于各种场景下的快速成员检测。它特别适合用来处理大规模数据集的查询操作,尤其是在需要判断某个元素是否属于某一集合的情况下。以下是关于如何利用布隆过滤器防止缓存穿透的具体方案: #### 工作原理概述 布隆过滤器的核心是由一个长度固定的比特数组以及多个独立的哈希函数组成[^1]。当向其中插入新元素时,这些哈希函数会分别计算出对应的位置索引并将相应位置上的值置为1;而在查找阶段,则只需验证所有指定位置是否均为1即可得出结论——如果存在任何一个零值,则表明目标对象肯定不属于当前集合。 然而值得注意的是,由于可能存在不同元素经过各自不同的哈希运算后指向完全相同的几个位的情况发生冲突从而造成假阳性结果的现象,即对于那些实际上并不存在于原始集合内的项目也可能被判别成“可能包含”。尽管如此,只要合理控制参数配置如预期最大容量N与允许误差率p之间关系等因素的影响程度,就可以使得最终产生的误判几率保持在一个较低水平范围内接受范围之内[^2]。 #### Java 实现案例分析 下面给出一段基于 Google Guava 库实现的一个简单例子展示怎样构建这样一个用于预防缓存击穿现象出现的基础框架结构: ```java // 导入必要的类库 import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class CachePenetrationPrevention { // 定义静态变量保存全局唯一实例化的布隆过滤器对象 private static final BloomFilter<String> BLOOM_FILTER; static{ // 构造方法调用前先完成初始化工作 int expectedInsertions = 100000; // 预估最多容纳多少条记录 double fpp = 0.01d; // 可容忍的最大错误概率百分比 // 调用工厂模式创建具体的布隆过滤器实体 BLOOM_FILTER = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), expectedInsertions, fpp); // 将一些已知有效的key预先加载进去形成基础底座 preloadKnownKeys(); } /** * 模拟预填充部分常见合法请求路径作为白名单参照物 */ private static void preloadKnownKeys(){ String[] knownValidRequests={"itemA","productB","serviceC"}; for(String key : knownValidRequests){ putIntoBloomFilter(key); } } /** * 把给定字符串形式的关键字加入到布隆过滤器当中去 * @param key 待录入的新关键字 */ public static void putIntoBloomFilter(String key){ if(!mightContainInBloomFilter(key)){ BLOOM_FILTER.put(key); } } /** * 测试传进来待测关键词是否有可能已经收录过来了呢? * @param key 输入测试的目标关键字 * @return 返回布尔类型的判定答案 */ public static boolean mightContainInBloomFilter(String key){ return BLOOM_FILTER.mightContain(key); } } ``` 在这个示例代码片段里展示了完整的生命周期管理流程图谱:从最开始定义好固定规格大小直至最后提供对外公开接口供其他模块调用为止整个过程一览无余。通过这种方式不仅可以有效减少不必要的数据库负载压力而且还能大幅提升整体响应速度体验效果明显优于传统单一层次防护措施单独作战所能达到的高度。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值