Bloom过滤器

Bloom过滤器

  • 一、概述
  • 二、原理
  • 三、优缺点
    • 1. 优点
    • 2.缺点
  • 四、Bloom过滤器在比特币中的应用
  • 五、项目应用步骤
    • 1. pom.xml引入依赖
    • 2. 样例代码
  • 六、Java版简易实现

一、概述

Bloom过滤器是一个允许用户描述特定的关键词组合而不必精确表述的基于概率的过滤方法。它能让用户在有效搜索关键词的同时保护他们的隐私。
1970年,它由布隆提出的。实际上它是由一个很长的二进制向量和一系列随意映射函数组成。它是一种基于概率的数据结构,主要用来判断某个元素是否在集合内,它具有运行速度快(时间效率),占用内存小的优点(空间效率),但是有一定的误识别率和删除困难的问题。它能够告诉你某个元素一定不在集合内或可能在集合内。
在比特币简单支付验证节点(SPV节点)里,这一方法被用来向对等节点发送交易信息查询请求,同时交易地址不会被暴露。
在设计网络爬虫时,我们用它来判断一个网址是否已经被访问过。
反垃圾邮件时,用它来判断一个邮件地址是否在数十亿个垃圾邮件黑名单列表中。
它还被用于解决缓存穿透问题……

比特币节点小故事

打个比方来说,每个全节点就像是一个在陌生城市里的游客,他带着一张包含每条街道、每个地址的详细地图。
相比之下,SPV节点就像是这名陌生城市里的游客只知道一条主干道的名字,通过随机询问该城市的陌生人来获取分段道路指示。
虽然两种游客都可以通过实地考察来验证一条街是否存在,但没有地图的游客不知道每个小巷中有哪些街道,也不知道附近还有什么其他街道。没有地图的游客在“教堂街23号”的前面,并不知道这个城市里是否还有其他若干条“教堂街23号”,也不知道面前的这个是否是要找的那个。
对他来说,最好的方式就是向足够多的人问路,并且希望其中一部分人不是要试图抢劫他。

二、原理

Bloom过滤器的实现是由一个可变长度(N)的二进制数组(N位二进制数构成一个位域)和数量可变(M)的一组哈希函数组成。这些哈希函数的输出值始终在1和N之间,该数值与二进制数组相对应。并且该函数为确定性函数,也就是说任何一个使用相同Bloom过滤器的节点通过该函数都能对特定输入得到同一个的结果。Bloom过滤器的准确性和私密性能通过改变长度(N)和哈希函数的数量(M)来调节。
下面,我用一个小型的十六位数组和三个哈希函数来演示Bloom过滤器的应用原理。
在这里插入图片描述
Bloom过滤器数组里的每一个数的初始值为零。关键词被加到Bloom过滤器中之前,会依次通过每一个哈希函数运算一次。该输入经第一个哈希函数运算后得到了一个在1和N之间的数,它在该数组(编号依次为1至N)中所对应的位被置为1,从而把哈希函数的输出记录下来。接着再进行下一个哈希函数的运算,把另外一位置为1;以此类推。当全部M个哈希函数都运算过之后,一共有M个位的值从0变成了1,这个关键词也被“记录”在了Bloom过滤器里。
在这里插入图片描述

增加第二个关键词就是简单地重复之前的步骤。关键词依次通过各哈希函数运算之后,相应的位变为1,Bloom过滤器则记录下该关键词。需要注意的是,当Bloom过滤器里的关键词增加时,它对应的某个哈希函数的输出值的位可能已经是1。这种情况下,该位不会再次改变。也就是说,随着更多的关键词指向了重复的位,Bloom过滤器随着位1的增加而饱和,准确性也因此降低了。该过滤器之所以是基于概率的数据结构,就是因为关键词的增加会导致准确性的降低。准确性取决于关键字的数量以及数组大小(N)和哈希函数的多少(M)。更大的数组和更多的哈希函数会记录更多的关键词以提高准确性。而小的数组及有限的哈希函数只能记录有限的关键词从而降低准确性。
在这里插入图片描述

为测试某一关键词是否被记录在Bloom过滤器中,我们将该关键词逐一代入各哈希函数中运算,并将所得的结果与原数组进行对比。如果所有的结果对应的位都变为了1,则表示这个关键词有可能已被该过滤器记录。之所以这一结论并不确定,是因为这些字节1也有可能是其他关键词运算的重叠结果。简单来说,Bloom过滤器正匹配代表着“可能是”。
在这里插入图片描述
上图是一个验证关键词“X”是否在前述Bloom过滤器中的图例。相应的比特位都被置为1,所以这个关键词很有可能是匹配的。
另一方面,如果我们代入关键词计算后的结果某位为0,说明该关键词并没有被记录在过滤器里。负匹配的结果不是可能,而是一定。也就是说,负匹配代表着“一定不是”。
在这里插入图片描述

上图是一个验证关键词“Y”是否存在于简易Bloom过滤器中的图例。图中某个结果字段为0,该字段一定没有被匹配。
比特币改进协议BIP0037里已经对Bloom过滤器的实现有所描述。具体请参见GitHub。

三、优缺点

1. 优点

常用的数据结构,如hashmap,set,bit array都能用来快速测试一个元素是否存在于一个集合中,相对于这些数据结构,Bloom过滤器有什么优势呢?
相比于哈希表、链表等数据结构,其空间和时间的优势明显。而且Bloom过滤器的插入、查询时间都是常数O(k),也就是说每次想要插入或查询一个元素是否在集合中时,只需要使用k个哈希函数对元素求值,并将对应的比特位标记或检查对应的比特位即可。
另外, 哈希函数相互之间没有关系,方便由硬件并行实现。Bloom过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

  • 对于hashmap,其本质上是一个指针数组,一个指针的开销是sizeof(void *),在64bit的系统上是64个bit,如果采用开链法处理冲突的话,又需要额外的指针开销,而对于Bloom过滤器来讲,返回可能存在的情况中,如果允许有1%的错误率的话,每个元素大约需要10bit的存储空间,整个存储空间的开销大约是hashmap的15%左右(数据来自维基百科)
  • 对于set,如果采用hashmap方式实现,情况同上;如果采用平衡树方式实现,一个节点需要一个指针存储数据的位置,两个指针指向其子节点,因此开销相对于hashmap来讲是更多的
  • 对于bit array,对于某个元素是否存在,先对元素做hash,取模定位到具体的bit,如果该bit为1,则返回元素存在,如果该bit为0,则返回此元素不存在。可以看出,在返回元素存在的时候,也是会有误判的,如果要获得和Bloom过滤器相同的误判率,则需要比Bloom过滤器更大的存储空间

Bloom过滤器可以表示全集,其它任何数据结构都不能;

  1. 全量存储但是不存储数据本身,适合有保密要求的场景
  2. 空间复杂度为O(m),不会随着元素增加而增加,占用空间少
  3. 插入和查询时间复杂度都是 O(k), 不会随着元素增加而增加,远超一般算法。

2.缺点

Bloom过滤器的缺点和优点一样明显。误判率是其中之一。随着存入的元素数量增加,误判率随之增加。但是如果元素数量太少,则使用散列表足矣。

另外,一般情况下不能从Bloom过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在Bloom过滤器里面,而Bloom过滤器只能给出可能在集合中或者一定不在集合中的回复,无法给出是否一定在集合中的回复。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

  • 相对于hashmap和set,Bloom过滤器在返回元素可能存在的情况中,有一定的误判率,这时候,调用者在误判的时候,会做一些不必要的工作,而对于hashmap和set,不会存在误判情况
  • 对于bit array,Bloom过滤器在插入和查找元素是否存在时,需要做多次hash,而bit array只需要做一次hash,实际上,bit array可以看做是Bloom过滤器的一种特殊情况。

在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。

  • 存在误算率,数据越多,误算率越高
  • 一般情况下无法从过滤器中删除数据
  • 二进制数组长度和 hash 函数个数确定过程复杂

四、Bloom过滤器在比特币中的应用

比特币中Bloom过滤器是在BIP-0037中提到。下面通过“SPV节点如何知道有多少钱”的问题来介绍Bloom过滤器在比特币中的应用。这个问题其实就是“SPV节点如何知道有多少UTXO”

在比特币网络中主要的两种节点类型:

  • 全节点:存放所有区块数据和交易
  • SPV节点:只下载区块头和交易相关部分的局部视图

我们假设,SPV节点最开始只存储了私钥,没有任何其他数据。那么它要获取跟自己地址相关的UTXO,只能向比特币网络中相邻的全节点询问。询问的方式有三种:

  1. 下载完整的区块链账本,自己查找
    这种方法很简单,也能隐藏用户的隐私(全节点无法知道SPV节点关联的钱包的地址)。但是在手机端是不现实的,每次用户需要下载上百G的区块链数据,才能知道自己钱包有多少钱,虽然保护了用户隐私,但是浪费了存储空间和带宽。所以这种方法不行,而这也是为什么有SPV的概念存在,中本聪也是考虑到移动支付的场景的。
  2. 直接告诉全节点自己钱包的所有地址,全节点返回所有跟钱包地址相关的UTXO
    这种方法直接等于是泄露了用户隐私,其他全节点就知道SPV节点所关联的钱包地址。但是好处是所要下载的数据少了很多,也更精确了。
  3. 告诉全节点部分自己钱包的地址信息,全节点返回可能相关的UTXO
    这种方法实际上就是采用Bloom过滤器的方法隐藏用户隐私,从而做到即保护用户隐私,又节省存储空间和带宽。我们知道布隆过滤器的两个特点:只能告诉你某个元素可能存在集合中以及某个元素一定不存在集合中。这里可以简单理解Bloom过滤器用来过滤不属于钱包的UTXO。

SPV节点会以Bloom过滤器的形式告诉相邻全节点自己地址信息,那么根据Bloom过滤器的特性,会有两种结果:

  • 没有通过Bloom过滤器过滤出来的UTXO,就【一定】不属于钱包地址
  • 通过Bloom过滤器过滤出来的UTXO,【可能】属于钱包地址
    这种方法在一定程度上保护用户隐私,节省了存储空间和带宽。但是根据Bloom过滤器的特点,随着钱包交易的UTXO越多,布隆过滤器误报率会越高,也就是相邻全节点返回正确的UTXO概率越低。

五、项目应用步骤

Bloom过滤器只是一个工具,不需要自己实现。本着有车轮就直接拿来用的原则,我们可以使用谷歌帮我们实现的BloomFilter,它封装的非常好,使用起来也非常简洁方便。

1. pom.xml引入依赖

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>

由于存在漏洞,不推荐使用该版本,请自行升级为最新版本。
当前最新版本为33.0.0-jre,由于网络不好,暂时没有拉取。

2. 样例代码

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * BloomFilter 测试
 *
 * @author Bin
 * @version 1.0
 * 2023/12/23
 */
public class BloomFilterTest {
    public static void main(String[] args) {
        int size = 100_0000;
        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), size);
//        filter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01);
//        filter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001);

        System.out.println("初始化Bloom过滤器,添加[1-" + size + "]中的数据到过滤器中");
        for (int i = 1; i <= size; i++) {
            filter.put(i);
        }

        test(filter, 1, size);
        test(filter, size + 1, size * 2);
    }

    private static void test(BloomFilter<Integer> filter, int start, int end) {
        int exist = 0;
        int exclude = 0;
        for (int i = start; i <= end; i++) {
            if(filter.mightContain(i)) {
                exist ++;
            } else {
                exclude ++;
            }
        }

        String str = "逐个判断[%d - %d]中的数据,被判为存在和不存在的个数分别是:%d / %d\r\n";
        System.out.printf(str, start, end, exist, exclude);
    }
}

六、Java版简易实现

虽说车轮不用重复造,但是想了解底层除了看源码,还就是自己造轮子。

Talk Is Cheap, Show Me The Code.


import java.util.BitSet;

/**
 * 简易版本Bloom Filter
 *
 * @author Bin
 * @version 1.0
 * 2023/12/23
 */
public class BloomFilter {
    /** 二进制数组 */
    private final BitSet bits;
    /** 二进制向量(数组)的位数 */
    private final int size;
    /** 用于生成信息指纹的随机数 */
    private final int[] seeds;

    public BloomFilter() {
        this(Integer.MAX_VALUE, new int[]{2, 3, 5, 7, 11}); // 默认大小为全部整数,种子为质数
    }

    public BloomFilter(int size, int[] seeds) {
        if (size < 1) {
            throw new IllegalArgumentException("Size must be greater than zero");
        }
        this.size = size;
        this.seeds = seeds;
        this.bits = new BitSet(size);
    }

    public void add(int item) {
        add(Integer.toString(item));
    }

    public void add(String item) {
        for (int seed : seeds) {
            int hash = hashFunction(seed, item);
            int index = hash % size;
            bits.set(index, true);
        }
    }

    public boolean contains(int item) {
        return contains(Integer.toString(item));
    }

    public boolean contains(String item) {
        if (item == null) {
            return false;
        }
        boolean result = true;
        for (int seed : seeds) {
            int hash = hashFunction(seed, item);
            int index = hash % size;
            result &= bits.get(index);
        }
        return result;
    }

    private int hashFunction(int seed, String item) {
        int hash = 0;
        for (char c : item.toCharArray()) {
            hash += seed * c;
        }
        return Math.abs(hash);
    }

    public static void main(String[] args) {
        BloomFilter filter = new BloomFilter();
        // 存入数据
        int size = 1000_0000;
        for (int i = 0; i < size; i++) {
            filter.add(i);
        }

        // 查看已有数据是否存在情况
        int count = 0;
        for (int i = 0; i < size; i++) {
            if(filter.contains(i)) {
                count ++;
            }
        }
        System.out.println("count=" + count);

        // 查看其它数据是否存在情况
        count = 0;
        for (int i = size; i < size * 2; i++) {
            if(filter.contains(i)) {
                count ++;
            }
        }
        System.out.println("count=" + count);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/266899.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解Vue3中的内置组件(transition)

本文主要介绍Vue3中的内置组件&#xff08;transition&#xff09;的普通写法和setup写法。 目录 一、在普通写法中使用内置组件&#xff08;transition&#xff09;二、在setup写法中使用内置组件&#xff08;transition&#xff09;三、使用注意项 在Vue3中&#xff0c;内置了…

Linux poll 和 select 机制

poll select 介绍 使用非阻塞 I/O 的应用程序常常使用 poll, select, 和 epoll 系统调用. poll, select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个 或多个文件而不阻塞. 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来 读或写.…

Nessus详细安装-windows (保姆级教程)

Nessus描述 Nessus 是一款广泛使用的网络漏洞扫描工具。它由 Tenable Network Security 公司开发&#xff0c;旨在帮助组织评估其计算机系统和网络的安全性。 Nessus 可以执行自动化的漏洞扫描&#xff0c;通过扫描目标系统、识别和评估可能存在的安全漏洞和弱点。它可以检测…

使用 Spring Boot + MyBatis开发需要注意的事项以及开发模版

前言&#xff1a; 注意&#xff0c;本篇不适用于有相关开发经验的开发者&#xff0c;作为一个在职开发者&#xff0c;我经常在完成从0-1的模块&#xff0c;也就是从数据库表开始到创建实体类&#xff0c;以及dao层&#xff0c;Service层等业务需要添加相关注解&#xff0c;这样…

使用office打开word文档时候提示错误:0x426-0x0的解决方案

在使用office打开word文档时候提示错误&#xff1a;0x426-0x0。如下图&#xff1a; 昨天还用的好好的&#xff0c;怎么今天就不行了&#xff1f;为什么呢&#xff1f; 更多工作中遇到问题见&#xff1a;凯哥BK 这个错误导致office无法启动通常是由于office软件所依赖的服务无…

vue的表单收集案例

Vue的表单收集案例 这只是最基础的表单收集&#xff0c;并未涉及到element-ui。 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>收集表单数据</title><script type"text/javascript" src"../js…

Hago 的 Spark on ACK 实践

作者&#xff1a;华相 Hago 于 2018 年 4 月上线&#xff0c;是欢聚集团旗下的一款多人互动社交明星产品。Hago 融合优质的匹配能力和多样化的垂类场景&#xff0c;提供互动游戏、多人语音、视频直播、 3D 虚拟形象互动等多种社交玩法&#xff0c;致力于为用户打造高效、多样、…

物理模拟重力 斜抛运动计算 抛物线计算

物理模拟重力 斜抛运动计算 抛物线计算 一、介绍二、原理三、实现如下PhysicsUtil.cs 工具类Missile.cs 四、资源分享 一、介绍 模拟Unity原始重力系统进行重写&#xff0c;可是实现发射到指定目标位置并能继续当前力进行自身的弹力与摩擦继续运动 二、原理 将Unity原始不受控…

word2003 open word2007+

Win 7 C:\Documents and Settings\Administrator\Application Data\Microsoft\Templates 还是不行&#xff0c;重装office2003吧&#xff0c;再安装转换插件&#xff0c;但是再高版本好像没转换工具

【Linux】进程管理

ps&#xff1a;报告当前进程快照。top&#xff1a;显示任务。kill&#xff1a;给一个进程发送信号。shutdown&#xff1a;关机或重启系统。 一个程序可以发动另一个程序被表述为一个父进程可以产生一个子进程&#xff0c;内核维护每个进程的信息&#xff0c;以此来保持事情有序…

【新版】软考 - 系统架构设计师(总结笔记)

个人总结学习笔记&#xff0c;仅供参考&#xff01;&#xff01;&#xff01;! →点击 笔者主页&#xff0c;欢迎关注哦&#xff08;互相学习&#xff0c;共同成长&#xff09; 笔记目录 &#x1f4e2;【系统架构设计系列】系统架构设计专业技能 计算机组成与结构操作系统信…

如何快速实现地源热泵远程监控

地源热泵远程监控解决方案 一、项目背景 山东省潍坊市盛世花园小区地源热泵项目是一个先进的供暖与制冷系统&#xff0c;旨在为整个小区提供高效且节能的温控服务。该系统主要由地下管道网络、地源热泵单元以及室内分配系统组成。 针对现有的地源热泵系统的管理和监控问题&a…

1162字符串逆序

一&#xff1a;题目 二.思路分析 1.如果不用递归&#xff0c;可以输入字符串后&#xff0c;再逆序输出&#xff0c;但是题目要求使用递归 2.使用递归&#xff1a; 2.1输入字符&#xff0c;直到输入的字符是‘&#xff01;’&#xff0c;停止输入&#xff0c;否则继续输入&…

Redis数据一致解决方案

文章目录 前言技术积累查询缓存业务流程更新缓存业务流程 更新缓存问题解决方案写在最后 前言 当前的应用服务很多都有着高并发的业务场景&#xff0c;对于高并发的解决方案一般会用到缓存来降低数据库压力&#xff0c;并且还能够提高系统性能减少请求耗时&#xff0c;比如我们…

AndroidStudio无法新建Java工程解决办法

我用的 AS 版本是 Android Studio Giraffe | 2022.3.1 Build #AI-223.8836.35.2231.10406996, built on June 29, 2023 以往新建工程都是 New project >> Empty Activity &#xff0c; 有个选择 Java 还是 Kotlin 语言的选项&#xff0c; 之后会默认生成一个 MainActi…

系列三、安装RocketMQ(单机版)

一、安装RocketMQ&#xff08;单机版&#xff09; 1.1、前置准备 通过前面系列一、MQ简介、系列二、RocketMQ简介的文章我们知道RocketMQ是用Java语言编写的&#xff0c;所以在安装RocketMQ之前&#xff0c;需要保证Linux中的JDK是已经安装好了的&#xff0c;要不然无法安装&a…

java流浪动物保护系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web 流浪动物保护系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql…

3.java——继承及拓展(保姆级别教程,万字解析,匠心制作)

三.继承——节省了共有属性和方法的代码&#xff1a;语法 class Student extends Person 1.继承基础 1.继承首先是面向对象中非常强的一种机制&#xff0c;他首先可以复用代码&#xff08;name ,age&#xff09;&#xff0c;让我们的获得了Person全部功能和属性&#xff0c;只…

使用keytool查看Android APK签名

文章目录 一、找到JDK位置二、使用方法2.1 打开windows命令行工具2.2 查看签名 三、如何给APK做系统签名呢? 一、找到JDK位置 安卓AS之后&#xff0c;可选择继续安装JDK&#xff0c;如本文使用amazon版本默认位置&#xff1a;C:\Users\66176.jdks\corretto-1.8.0_342可通过自…

智能优化算法应用:基于北方苍鹰算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于北方苍鹰算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于北方苍鹰算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.北方苍鹰算法4.实验参数设定5.算法结果6.…