布隆过滤器学习和部分实现

1. 布隆过滤器

1.1简介

布隆过滤器实际上是一种数据结构,它可以实现在某些特定场景下的高效的查找。这种高效体现在,**在很低的存储空间上进行快速的定位。**我们拿黑名单举例,在搜索引擎中,想定位某个网站是否是黑名单,把待查询的URL输入布隆过滤器中,此时如果是黑名单,那么一定可以查出来。

布隆过滤器存在一定的失误率,即一个输入不在过滤器中,我也有可能判断其在过滤器中。拿黑名单举例,如果一个网站是黑名单,那么它一定会被过滤,如果不是,它也有可能会被过滤。这和布隆过滤器的原理是有关系的。

布隆过滤器需要对hash函数有个认识。hash函数可以将输入的任意多数字、字符得到一个hashcode,长度为是一个固定的值,通常是16的整数倍。那么问题来了,一个函数的输入是不限定的,输出是一个确定的个数,所有一定会存在多个输入对应一个输出,这是显而易见的,这在hash函数中有个高大上的叫法叫碰撞,也很形象。说到这,我们大概理解了:同一个数据输入hash函数一定可以得到相同的输出,不同的数据输入hash函数可能会得到相同的输出,这种现象称为碰撞。

下图简单描述了hash函数与HashMap:
在这里插入图片描述

Hash函数可以说是大名鼎鼎了,hash函数的特点如下:

  • 单向性:在hash函数中,无法从输出结果推导出输入结果,保证了安全性,比如MD5码(一种摘要算法)和SHA。

  • 碰撞约束: 不能同时找到两个不同的输入使其输出结果一致。

为什么在计算机中使用hash函数?

  • 进过Hash函数的处理的数据会均匀的分布在一个区域内,比如一个数据集中许多数据集中出现在某些部分,但是在别的部分很稀疏,这些数据经过hash函数处理就可以几乎以一个平均的状态分布在整个数据集上,前提是数据集相当大。这一点是hash函数的设计导致的,这涉及到严谨的数学证明,这里不讨论。

  • 输出的结果是确定的。我们可以通过查找hashcode来确定一个数据是否存在于集合中,布隆过滤器就是利用这一点。

  • 计算机中可以做到对一个数据进行hash处理的时间复杂度为O(1),java中为O(logN),开销很小

1.2 原理

说了这么多,布隆过滤器到底是如何工作的呢?首先布隆过滤器中的存储结构在逻辑上来说为位数组,常用基础数据类型来实现这个数组。为了方便理解,这里举例数据为字符串。

操作过程:

输入的字符串会经过K个hash函数处理(这些hash函数必须是无关的,这并不难得到),得到了K个结果,把这些结果对arr进行hashMap操作,就会得到在数组中的K个位置,把这K个位置标位1,当输入了许多字符后,数组中有许多部分已经被标志位1了。

当新来一个字符串时,继续对其进行重复的操作,观察这K个位置是否有不为1的位置,如果有则不被过滤,否则被过滤。

废话不多说,上图:
在这里插入图片描述
下面说几个细节:

  • 首先,数组长度必须随着输入样本量的而调整,当输入量很大,而数组不是很大,会导致整个数组几乎所有位置都是1,无论什么字符串输入都会被过滤。
  • 过滤器存在失误率。这和数组开辟的长度和样本量有关系。即我新输入的字符串的K个位置都被其他字符串给标记过了,那么我只会把这个字符串过滤掉。

相关公式:
M = − n ∗ l n P ( l n 2 ) 2 M = - \frac{n * lnP}{(ln2)^2} M=(ln2)2nlnP
其中n为样本的数量,p为可以接受的失误率,M为开辟的数组长度。
K = l n 2 ∗ m n , 其 中 K 为 h a s h 函 数 的 个 数 K = ln2 * \frac{m}{n},其中K为hash函数的个数 K=ln2nm,Khash
在实际的计算中,我们会选择最优的情况,即我们计算出来M和K大概率不是整数,我们此时向上取整,即我们的数组多开辟了一些空间,哈希函数也比理论上多一点,那么我们在查找过程中,出现失误的概率就会减少(想想导致失误的原因)。真实的失误率为:
P = ( 1 − e − n ∗ k M ) K , p 为 失 误 率 P = (1 - e ^{\frac{-n * k}{M}})^K ,p为失误率 P=(1eMnk)Kp

1.3 布隆过滤器的优势与应用

当我们需要处理几亿条数据时,如果采用hashmap来存储这个记录,容量巨大,而采用布隆过滤器则会大大降低存储的空间。

相对于简单的数据结构存储来说,布隆过滤器的通过使用多个hash函数使得错误率变得很低。

布隆过滤器可以应用在:

  • 黑名单判断
  • 网络爬虫判断网站是否被处理过了
  • 垃圾邮件过滤等

1.3代码实现

public class BloomFilter {
    public static int NUM = 1024;
    private static int[] arr = new int[NUM]; //可以表示1024 * 32个数据
    //这里的code是已经对M取过模的数字了,M为逻辑上的位数组的长度
    public static void setFlag(int code)
    {
        //定位其所在的整数下标
        int index = code / 32;
        //定位其坐在整数的字节下标
        int bitIndex = index % 32;
        arr[index] = (arr[index] | (1 << bitIndex));

    }
}

参考:

  1. 左程云算法课
  2. https://www.jianshu.com/p/88c6ac4b38c8
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值