• 布隆过滤器--你可以永远相信布隆


    一看到布隆这个名字想必大多数人都想到了LOL里的英雄布隆,作为一名常玩辅助的玩家,我也喜欢玩布隆。

    哈哈哈,言归正传,我们来认识下一另一个布隆。

    image-20221020093506925

    1. 什么是布隆过滤器

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

    我们可以先假定布隆过滤器就是一个set集合,判断某个元素是否存在这个集合中。

    有同学可能会问:那我们直接用HashMap去检索元素行不行呢,为啥还要用布隆过滤器呢?HashMap 确实可以帮助我们实现这个功能,而且 HashMap 通过一次运算就能确定某个元素的位置,也就可以帮助我们检查某个元素是否在一个集合中。那么我们接下来再思考一个问题:如果这个元素集合中有十亿个随机数,那我们怎样来判断某个数是否存在呢?首先就会带来非常大的空间消耗。

    为了解决这个问题,我们的布隆过滤器出现了。

    但是由于它是基于概率的,因此它存在一定的误判率,它的Contains()操作如果返回true只是表示元素可能存在集合内,返回false则表示元素一定不存在集合内。因此适合用于能够容忍一定误判元素存在集合内的场景,比如缓存。

    它一秒能够进行上百万次操作(主要取决于哈希函数的速度),并且1亿数据在误判率1%的情况下,只需要114MB内存。

    2. 布隆过滤器的原理

    2.1 数据结构

    布隆过滤器的数据结构是一个位向量,也就是一个由01所组成的向量(下面是一个初始向量):

    image-20221020100501964

    2.2 添加元素

    每个元素添加进布隆过滤器前,都会经过多个不同的哈希函数,计算出不同的哈希值,然后映射到位向量上,也就是对应的位上面置1:

    image-20221020101332058

    2.3 判断是否存在

    判断元素是否存在也是如上图流程,根据哈希函数映射的位置,判断所有映射位置是否都为1,如果是则元素可能存在,否则元素一定不存在。

    由于不同的值通过哈希函数之后可能会映射到相同的位置,因此如果一个不存在的元素对应的位位置都被其他元素所设置位1,则查询时就会误判:

    image-20221020101432107

    假设上图元素3334并没有加入集合,但是由于它映射的位置已经被其他元素所映射,则查询时会误判。

    2.4 删除元素

    布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除!关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是带来连锁反应

    3. 布隆过滤器的优缺点

    优点:

    • 时间复杂度低,增加和查询元素的时间复杂度位O(N)(N位哈希函数的个数,通常情况下比较小)
    • 保密性强,布隆过滤器不存储元素本身
    • 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的。

    缺点:

    • 存在一定的误判率,但可以通过调整参数来降低
    • 无法获取元素本身
    • 很难删除元素

    4布隆过滤器的应用

    布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在,利用这个判断是否存在的特点可以做很多有趣的事情。

    • 解决Redis缓存穿透问题(面试重点)
    • 邮件过滤,使用布隆过滤器来做邮件黑名单过滤
    • 对爬虫网址进行过滤,爬过的不再爬
    • 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到)
    • HBase\RocksDB\LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求

    5 模拟实现布隆过滤器

    定义一个hash函数

    /**
     * 构建hash函数
     */
    public class HashFunction {
        // 比特位的大小
        private int size;
        // hash算法的种子
        private int seed;
    
        public HashFunction() {
        }
    
        public HashFunction(int size, int seed) {
            this.size = size;
            this.seed = seed;
        }
    
        public int hash(String value) {
            int result = 0;
            int len = value.length();
            // 执行hash算法
            for (int i = 0; i < len; i++) {
                result = result * seed + value.charAt(i);
            }
            return (size - 1) & result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    测试类

    import java.util.BitSet;
    
    public class MyBloomFilter {
        // 一个长度为10亿的比特位 2^30
        private static final int DEFAULT_SIZE = 256 << 22;
    
        // 为了降低错误率,使用假发hash算法,定义一个8个元素的质数数组作为种子
        private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};
    
        // 构建了八个不同的hash算法
        private static HashFunction[] functions = new HashFunction[seeds.length];
    
        // 初始化布隆过滤器的bitmap,即位数组
        private static BitSet bitSet = new BitSet(DEFAULT_SIZE);
    
        // 添加数据
        public static void add(String value) {
            if (value != null) {
                for (HashFunction f : functions) {
                    // 计算hash值并修改bitmap中相应位置位true
                    bitSet.set(f.hash(value), true);
                }
            }
        }
    
        // 判断元素是否存在
        public static boolean contains(String value) {
            if (value == null)
                return false;
            boolean ok = true;
            for (HashFunction f : functions) {
                // 只要有一个hash值不存在就退出循环
                if (!bitSet.get(f.hash(value))) {
                    ok = false;
                    break;
                }
            }
            return ok;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < seeds.length; i++) {
                functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);
            }
    
            // 添加1亿个元素
            for (int i = 0; i < 100000000; i++) {
                add(String.valueOf(i));
            }
    
            String id = "123456789";
            add(id);
    
            System.out.println("123456789 是否存在:" + contains(id));
            System.out.println("234567890 是否存在:" + contains("234567890"));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    image-20221020155625954

    效果还是可以的。

    笔记参考:

    布隆(Bloom Filter)过滤器——全面讲解,建议收藏

    布隆过滤器:一种低空间成本的判断元素是否存在的方式 - 掘金 (juejin.cn))

    大聪明教你学Java | 深入浅出聊布隆过滤器(Bloom Filter) - 掘金 (juejin.cn))

  • 相关阅读:
    atguigu----kafka概述
    21天学习挑战赛(3)设备树二进制文件DTB解析,DTB信息转化为 device_node 结构
    【云原生】Prometheus+Grafana on K8s 环境部署
    CH340 各型号的区别
    检验科LIS系统,即实验室信息管理系统
    大语言模型系列
    网络编程:select的用法和原理
    C++中引用类型做做右值
    [附源码]SSM计算机毕业设计血库管理系统JAVA
    叁[3],感兴趣区域ROI
  • 原文地址:https://blog.csdn.net/weixin_53029342/article/details/127423508