初识布隆过滤器BloomFilter

一、认识:

      布隆过滤器,英文叫BloomFilter,可以说是一个二进制向量和一系列随机映射函数实现。 可以用于检索一个元素是否在一个集合中。

二、原理:

数据存储示意图-->

说明:bit数组中以n个索引位(n个不同的hash算法后的产生的索引)表示一个数据

小结:

  • 布隆过滤器是用于判断一个元素是否在集合中。通过一个位数组和N个hash函数实现。
  • 注意:布隆过滤器有宁可错杀一百,也不能放过一个的特质,只会误报,不会漏报
  • 优点:
  1. 空间效率高,所占空间小。
  2. 查询时间短。
  • 缺点:
  1. 元素添加到集合中后,不能被删除。
  2. 有一定的误判率

三、使用场景:

  • 网页爬虫对URL的去重,避免爬去相同的URL地址。
  • 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否是杀垃圾邮箱。
  • 解决数据库缓存击穿,黑客攻击服务器时,会构建大量不存在于缓存中的key向服务器发起请求,在数据量足够大的时候,频繁的数据库查询会导致挂机。
  • 秒杀系统,查看用户是否重复购买

四、实现方式:

根据布隆过滤器的原理,其实各种语言都有相应的实现,比如java、python、scala、redis、lua等等,各自有各自的优化,目的都是为了降低误报率和空间利用率。

1)Java实现

import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.BitSet;

public class CustomBloomFilter {

    //数据个数
    private int size;

    //bit[]长度
    private int bitSize;
    //bit[]容器
    private BitSet bitMap;

    //hash函数个数
    private int hashNum;
    //误判率
    private double cap;


    /**
     *
     * @param size 数据量
     * @param cap 误判率
     */
    public CustomBloomFilter(int size, double cap) {
        this.size = size;
        this.cap = cap;
    }

    /**
     * 初始化数据至容器
     */
    public synchronized void init(){
        if(this.bitSize == 0){
            this.bitSize = (int)((-size * Math.log(this.cap)) / (Math.pow(Math.log(2),2)));
        }
        if(this.hashNum == 0){
            this.hashNum = Math.max(1,(int)Math.round(this.bitSize / this.size * Math.log(2)));
        }
        if(this.bitMap == null) {
            bitMap = new BitSet(this.bitSize);
        }
        System.out.println("this.bitSize :" + this.bitSize);
        System.out.println("this.hashNum :" + this.hashNum);

    }

    /**
     * 添加元素至容器中
     * @param ele
     */
    public void add(String ele){

        if(bitMap == null){
            init();
        }
        int[] posArr = getIndexs(ele);

        for (int t : posArr) {
            bitMap.set(t,true);
        }
    }

    /**
     * 判断元素是否存在
     * @param ele 元素
     * @return
     */
    public boolean isExist(String ele){
        int[] posArr = getIndexs(ele);
        boolean flag = true;
        for (int t : posArr) {
            flag = flag && bitMap.get(t);
        }
        return flag;
    }

    /**
     * 获取元素计算得出的下标集
     * @param ele 元素
     * @return
     */
    public int[] getIndexs(String ele){
        int[] retArr = new int[this.hashNum];
        for (int i = 0; i < this.hashNum; i++) {
            retArr[i] = HashUtil.md5Hash(ele + i) % this.bitSize;//运用算法得出下标
        }
        return retArr;
    }


    public static class HashUtil{

        public static int md5Hash(String ele){
            try {
                // 生成一个MD5加密计算摘要
                MessageDigest md = MessageDigest.getInstance("MD5");
                // 计算md5函数
                md.update(ele.getBytes());
                // digest()最后确定返回md5 hash值,返回值为8位字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
                // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
                //一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)
                String s = new BigInteger(1, md.digest()).toString(16);
                return Arrays.hashCode(s.getBytes()) & Integer.MAX_VALUE;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    }


    public static void main(String[] args) {

        CustomBloomFilter bloomFilter = new CustomBloomFilter(10000,0.003);

        for (int i = 0; i < 10000; i++) {
            bloomFilter.add("abc" + i);
        }

        int count = 0;
        for (int i = 0; i < 20000; i++) {
            if(bloomFilter.isExist("abc" + i)){
                count++;
            }
        }
        System.out.println("count:" + count);
    }

}

2)Redis实现

RedisBloom模块不仅仅实现了布隆过滤器,还实现了 CuckooFilter(布谷鸟过滤器),以及 TopK 功能。CuckooFilter 是在 BloomFilter 的基础上主要解决了BloomFilter不能删除的缺点。

#安装RedisBloom模块
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
make #编译 会生成一个rebloom.so文件
redis-server --loadmodule /path/to/rebloom.so
redis-cli -h 127.0.0.1 -p 6379
复制代码
127.0.0.1:8001>  bf.reserve bloom_filter_test 0.0000001 1000000
OK
127.0.0.1:8001>  bf.reserve bloom_filter_test 0.0000001 1000000
(error) ERR item exists
127.0.0.1:8001>
127.0.0.1:8001>
127.0.0.1:8001> bf.add bloom_filter_test key1
(integer) 1
127.0.0.1:8001> bf.add bloom_filter_test key2
(integer) 1
127.0.0.1:8001>
127.0.0.1:8001> bf.madd bloom_filter_test key2 key3 key4 key5
1) (integer) 0
2) (integer) 1
3) (integer) 1
4) (integer) 1
127.0.0.1:8001> bf.exists bloom_filter_test key2
(integer) 1
127.0.0.1:8001> bf.exists bloom_filter_test key3
(integer) 1
127.0.0.1:8001> bf.mexists bloom_filter_test key3 key4 key5
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:8001>

 注释:

  • bloom filter定义

BF.RESERVE {key} {error_rate} {capacity}
     使用给定的期望错误率和初始容量创建空的Bloom过滤器(如果不存在的话)。如果打算向Bloom过滤器中添加许多项,则此命令非常有用,否则只能使用BF.ADD 添加项。
初始容量和错误率将决定过滤器的性能和内存使用情况。一般来说,错误率越小(即对误差的容忍度越低),每个过滤器条目的空间消耗就越大。

  • BF.ADD {key} {item}

单条添加元素
向Bloom filter添加一个元素,如果该key不存在,则创建该key(过滤器)。如果项是新插入的,则为“1”;如果项以前可能存在,则为“0”。

  • BF.MADD {key} {item} [item...]

批量添加元素
布尔数(整数)的数组。返回值为0或1的范围的数据,这取决于是否将相应的输入元素新添加到过滤器中,或者是否已经存在。

  • BF.EXISTS {key} {item}

判断单个元素是否存在
如果存在,返回1,否则返回0

  • BF.MEXISTS {key} {item} [item...]

判断多个元素是否存在
布尔数(整数)的数组。返回值为0或1的范围的数据,这取决于是否将相应的元是否已经存在于key中。

  • bf.insert

bf.insert{key} [CAPACITY {cap}] [ERROR {ERROR}] [NOCREATE] ITEMS {item…}
该命令将向bloom过滤器添加一个或多个项,如果它还不存在,则默认情况下创建它。有几个参数可用于修改此行为。
key:过滤器的名称
capacity:如果指定了,应该在后面加上要创建的过滤器的所需容量。如果过滤器已经存在,则忽略此参数。如果自动创建了过滤器,并且没有此参数,则使用默认容量(在模块级指定)。见bf.reserve。
error:如果指定了,后面应该跟随着新创建的过滤器的错误率(如果它还不存在)。如果自动创建过滤器而没有指定错误,则使用默认的模块级错误率。见bf.reserve。
nocreate:如果指定,表示如果过滤器不存在,就不应该创建它。如果过滤器还不存在,则返回一个错误,而不是自动创建它。如果需要在创建过滤器和添加过滤器之间进行严格的分离,可以使用这种方法。将NOCREATE与容量或错误一起指定是一个错误。
item:指示要添加到筛选器的项的开头。必须指定此参数。

  • bf持久化操作

BF.SCANDUMP {key} {iter}

对bloom过滤器进行增量保存。这对于不能适应常规save和restore模型的大型bloom filter非常有用。
第一次调用这个命令时,iter的值应该是0。这个命令将返回连续的(iter, data)对,直到(0,NULL),以表示完成

  • blool filter数据类型的属性

   bf.debug

127.0.0.1:8001> bf.debug bloom_filter_test
1) "size:5"
2) "bytes:4194304 bits:33554432 hashes:24 hashwidth:64 capacity:1000200 size:5 ratio:1e-07"

3)Google guava实现:

 对于Google布隆过滤器来说,在不做任何设置的情况下,默认的误判率为0.03

 引入jar包

<dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>22.0</version>
</dependency>

 测试

         BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 10000,0.003);
         for (int i = 0; i < 10000; i++) {
            bloomFilter.put("abc" + i);
        }

        int count = 0;
        for (int i = 0; i < 20000; i++) {
            if(bloomFilter.mightContain("abc" + i)){
                count++;
            }
        }
        System.out.println("count:" + count);

 拓展:

该文章 对比了布隆过滤器和布谷鸟过滤器,相比布谷鸟过滤器,布隆过滤器有以下不足:

  • 查询性能弱 查询性能弱是因为布隆过滤器需要使用N个 hash函数计算位数组中N个不同的点,这些点在内存上跨度较大,会导致CPU缓存命中率低。
  • 空间利用效率低 在相同的误判率下,布谷鸟的空间利用率要高于布隆过滤器,能节省40%左右。
  • 不支持删除操作 这是布谷鸟过滤器相对布隆过滤器最大的优化,支持反向操作,删除功能。
  • 不支持计数

五、公式推导:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值