Java利用布隆过滤器实现快速检查元素是否存在

 更新时间:2022年10月24日 08:55:10   作者:指北君  
布隆过滤器是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。本文就来详细说说实现的方法,需要的可以参考一下

Guava BloomFilter

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

基本概念

当需要判断某个元素是否在某个数据集中时,一般会怎么做?

  • 将数据集封装成集合,比如List、Set等
  • 通过集合提供的API判断该元素是否存在于集合

这样的实现比较简单,同时通过现有的JDK都能很快达到目的,但是设想一下,如果上面说到的集合数据量非常的大,这样不仅会耗费较大的存储空间,同时 在集合中检索元素的时间复杂度也会随之增加。那么有没比较好的方法去实现判断元素是否存在这样的情形呢?

也就是布隆过滤器

通过一系列的Hash函数将元素映射到一个位阵列(Bit Array)中的多个点位上,判断元素是否存在,则是判断所有点位是不是都为1。然而,位阵列上都为1并不一定能够保证该元素一定存在,也有可能是其他元素Hash后落在了该点位上,这就是布隆过滤器的误判。

因此通过布隆过滤器我们可以确定:

  • 元素可能在集合中
  • 元素一定不在集合中

应用场景

网页爬虫时忽略已经判定的URL路径

邮箱通过设置过滤垃圾邮件

集合重复元素的判别,有效判断元素不在集合中

防止数据缓存时的缓存穿透问题

优缺点

优点

  • 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。
  • 布隆过滤器存储空间和插入/查询时间都是常数。
  • Hash函数相互之间没有关系,方便由硬件并行实现。
  • 布隆过滤器不需要存储元素本身,对保密要求非常严格的场合有优势。
  • 布隆过滤器可以表示全集,其它任何数据结构都不能。

缺点

  • 元素存在的误判
  • 一般情况下不支持元素(位阵列)的删除

实现原理

核心其实是元素如何存储?如何判断元素是否存在?核心方法就两个,一个“存”一个检查,里面涉及到了算法相关知识,感兴趣可以深入研究下其实现原理与思想。

put 将元素放入过滤器中,但不是存储

        public <T> boolean put(@ParametricNullness T object, Funnel<? super T> funnel, int numHashFunctions, LockFreeBitArray bits) {
            long bitSize = bits.bitSize(); // 位数组,可以通过redis来实现分布式的布隆过滤器
            long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong(); //通过funnel将对象转换成基本类型并计算64位hash
            int hash1 = (int)hash64; // 取低32位
            int hash2 = (int)(hash64 >>> 32); // 取高32位
            boolean bitsChanged = false;
            // 
            for(int i = 1; i <= numHashFunctions; ++i) {
                int combinedHash = hash1 + i * hash2;
                if (combinedHash < 0) {
                    combinedHash = ~combinedHash;
                }

                bitsChanged |= bits.set((long)combinedHash % bitSize);
            }

            return bitsChanged;
        }

mightContain 与put相似,计算的过程相同,不同的是值的判断

        public <T> boolean mightContain(@ParametricNullness T object, Funnel<? super T> funnel, int numHashFunctions, LockFreeBitArray bits) {
            long bitSize = bits.bitSize();
            long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
            int hash1 = (int)hash64;
            int hash2 = (int)(hash64 >>> 32);

            for(int i = 1; i <= numHashFunctions; ++i) {
                int combinedHash = hash1 + i * hash2;
                if (combinedHash < 0) {
                    combinedHash = ~combinedHash;
                }

                if (!bits.get((long)combinedHash % bitSize)) {
                    return false;
                }
            }

            return true;
        }

我们可以简单第理解其实现原理?比如现在有一个容器,我们定义为String[] bitArray = new String[26]作为位阵列, 现在有一堆由小写英文组成的元素,我们假定Hash算法为a-z到1~26的映射。

  • 现在有一个元素abc,hash后为1110000000...,保存到bitArray :1110000000...
  • 现在有一个元素cde, hash后为0011100000...,保存到bitArray :1111100000...
  • 现在又有一个新的元素ade,hash后同样为100110000...,很明显会认为该元素存在,这就是FFP

为什么判断元素一定不在集合中呢?很显然,如果一个元素存在,则该元素hash后的bit数组必须全部都是1,反之则不存在

示例

    @Test
    public void match(){
        BloomFilter filter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()),10000,0.2);
        List<String> ids = new ArrayList<>();

        IntStream.rangeClosed(1,10000).forEach(index->{
            String id = UUID.randomUUID().toString();
            ids.add(id);
            filter.put( id );
        });

        ids.forEach(id->{
            // 正常情况下全部失败,但是会有 20%的返回true
            System.out.println( id + ":" + filter.mightContain( id+1 ));
        });
    }

流程很简单:

  • 根据配置构建BloomFilter对象
  • 通过put方法,初始化数据到filter
  • 通过方法mightContain判断元素是否存在

结束语

BloomFilter虽然看起来简单,但是其内部的实现包含了很多的数学与算法知识,我们只是通过其简单的API就能各种复杂的功能。关于如何将目前说到的这些在具体的项目中进行实践与集成 后面会来介绍,首先我们能够先了解一些技术一起能解决上面问题,理解了原理与目的,使用也就不是难事。

到此这篇关于Java利用布隆过滤器实现快速检查元素是否存在的文章就介绍到这了,更多相关Java布隆过滤器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java子线程解决获取主线程的request对象问题

    java子线程解决获取主线程的request对象问题

    这篇文章主要介绍了java子线程解决获取主线程的request对象问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • Java 数据结构与算法系列精讲之KMP算法

    Java 数据结构与算法系列精讲之KMP算法

    在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么一回事,但总感觉有些地方自己还是没有完全懂明白。这两天花了点时间总结一下,有点小体会,我希望可以通过我自己的语言来把这个算法的一些细节梳理清楚,也算是考验一下自己有真正理解这个算法
    2022-02-02
  • 关于java.io.EOFException产生的原因以及解决方案

    关于java.io.EOFException产生的原因以及解决方案

    文章总结:EOFException异常通常发生在尝试从空的ObjectInputStream对象中读取数据时,解决方法是在finally语句中添加判断,确保objectInputStream不为空后再进行关闭操作,在处理1.txt文件为空的情况时,捕获EOFException可以避免程序终止,并且不会抛出空指针异常
    2025-01-01
  • Java利用InputStream类实现文件读取与处理

    Java利用InputStream类实现文件读取与处理

    在Java开发中,输入流(InputStream)是一个非常重要的概念,它涉及到文件读写、网络传输等多个方面,InputStream类是Java中输入流的抽象基类,定义了读取输入流数据的方法,本文将以InputStream类为切入点,介绍Java中的输入流概念及其应用,需要的朋友可以参考下
    2023-11-11
  • Java:

    Java:"失效"的private修饰符

    本文主要介绍Java 失效的private修饰符,这里整理了相关资料说明private 修饰符的作用,如何使用并与C++ 做比较,有兴趣的小伙伴可以参考下
    2016-08-08
  • Java设置session超时的几种方式总结

    Java设置session超时的几种方式总结

    这篇文章主要介绍了Java设置session超时的几种方式总结的相关资料,需要的朋友可以参考下
    2017-07-07
  • Java实现经典捕鱼达人游戏的示例代码

    Java实现经典捕鱼达人游戏的示例代码

    《捕鱼达人》是一款以深海狩猎为题材的休闲竞技游戏。本文将利用Java实现这一经典的游戏,文中采用了swing技术进行了界面化处理,需要的可以参考一下
    2022-02-02
  • SpringBoot工程搭建打包、启动jar包和war包的教程图文详解

    SpringBoot工程搭建打包、启动jar包和war包的教程图文详解

    这篇文章主要介绍了SpringBoot工程搭建打包、启动jar包和war包的教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Spring Boot项目添加外部Jar包以及配置多数据源的完整步骤

    Spring Boot项目添加外部Jar包以及配置多数据源的完整步骤

    这篇文章主要给大家介绍了关于Spring Boot项目添加外部Jar包以及配置多数据源的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-06-06
  • JAVA中HTTP基本认证(Basic Authentication)实现

    JAVA中HTTP基本认证(Basic Authentication)实现

    HTTP 基本认证是一种简单的认证方法,本文主要介绍了JAVA中HTTP基本认证(Basic Authentication),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07

最新评论