布隆过滤器和布谷鸟过滤器

原文链接:布隆过滤器和布谷鸟过滤器

布隆过滤器
介绍

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数,检查值是“可能在集合中”还是“绝对不在集合中”

  • 空间效率高:通常比精确数据结构占用更少的空间
  • 查询速度快:常数时间复杂度 O(1)
  • 误报率可控:通过调整哈希函数的数量和布隆过滤器的大小可控制误报率
  • 不能删除元素:一旦向布隆过滤器中添加了元素,则不能从中删除
原理

本质是由长度为 m 的向量或列表(仅包含 0、1),最初所有值均设为 0

为了将数据添加到布隆过滤器中,会提供 K 个不同的哈希函数,并将结果位置上对应位置设为“1”,使用多(此处假设为 3 个)个哈希函数得到多个索引值,如输入“semlinker”时,预计得到 2、4、6,将相应位置设为 1

再输入“kakuqo”时,哈希得到 3、4、7,此刻的 4 被标记了两次

当我们对值进行搜索时,先使用 3 个哈希函数对搜索值进行哈希运算,例如输入“fullstack”时,得到 2、3、7,相应位置都为 1,意味着可能已经插入到集合了。

布隆过滤器的误判率

  • n:已经添加的元素
  • k:哈希次数
  • m:布隆过滤器长度

应用场景
  • 网页爬虫对 URL 去重,避免爬取相同 URL
  • 反辣椒邮件,从数十亿个辣椒邮件列表中判断某邮箱是否为垃圾邮箱
  • Google BigTable,Apache HBbase 和 Apache Cassandra 使用布隆过滤器减少对不存在的行和列的查找
代码实现
package com.yingzi.data_structure;import java.util.BitSet;
import java.util.Random;public class BloomFilter {private final BitSet bitSet;private final int[] hashFunctions;public BloomFilter(int expectedElements, double falsePositiveRate) {// 计算位数组大小int bits = _optimalSize_(expectedElements, falsePositiveRate);// 创建位数组this.bitSet = new BitSet(bits);// 计算所需的哈希函数数量int hashCount = _optimalHashCount_(expectedElements, bits);this.hashFunctions = generateHashFunctions(hashCount);}private static int optimalSize(int expectedElements, double falsePositiveRate) {return (int) (-expectedElements * Math._log_(falsePositiveRate) / (Math._log_(2) * Math._log_(2)));}private static int optimalHashCount(int expectedElements, int bits) {return (int) (bits / expectedElements * Math._log_(2));}private int[] generateHashFunctions(int hashCount) {Random random = new Random();int[] hashes = new int[hashCount];for (int i = 0; i < hashCount; i++) {hashes[i] = random.nextInt();}return hashes;}public void add(String item) {for (int hash : hashFunctions) {int index = Math._abs_(hash ^ item.hashCode()) % bitSet.size();bitSet.set(index);}}public boolean mightContain(String item) {for (int hash : hashFunctions) {int index = Math._abs_(hash ^ item.hashCode()) % bitSet.size();if (!bitSet.get(index)) {return false;}}return true;}
}
  1. 构造函数 (BloomFilter): 接收期望的元素数量和期望的误报率。根据这些信息计算出合适的位数组大小和哈希函数数量。
  2. optimalSize 方法: 根据公式计算最优的位数组大小。
  3. optimalHashCount 方法: 根据公式计算最优的哈希函数数量。
  4. generateHashFunctions 方法: 生成指定数量的哈希函数。
  5. add 方法: 将元素添加到布隆过滤器中。对于每个哈希函数,计算出一个索引并设置该位。
  6. mightContain 方法: 检查一个元素是否可能存在于布隆过滤器中。对于每个哈希函数,检查相应的位是否被设置。如果所有相关的位都被设置,则认为该元素可能存在于布隆过滤器中。
public class BloomFilterExample {public static void main(String[] args) {// 假设我们期望有 1000 个元素,希望误报率小于 0.1%BloomFilter bloomFilter = new BloomFilter(1000, 0.001);// 添加一些元素String[] elementsToAdd = {"hello", "world", "java", "programming"};for (String element : elementsToAdd) {bloomFilter.add(element);}// 检查一些元素是否存在System._out_.println("Does 'hello' exist? " + bloomFilter.mightContain("hello")); // trueSystem._out_.println("Does 'world' exist? " + bloomFilter.mightContain("world")); // trueSystem._out_.println("Does 'nonexistent' exist? " + bloomFilter.mightContain("nonexistent")); // false}
}
变体

在海量数据处理的场景中,我们往往无法预测数据的规模,而重建过滤器的开销又过大,因此需要一个支持删除元素的过滤器,根据不同的实现方法,衍生以下变体

  • 计数布隆过滤器:不再使用一个计数器,而是使用一个计数器,删除一个元素时将对应位置的计数减 1,当计数为零时代表元素不存在,该方法虽然支持了删除,但空间随着计数器大小成倍增加
  • 阻塞布隆过滤器:多层级的布隆过滤器(类似 CPU 的多级缓冲),将集合分为多个布隆过滤器(每个过滤器相互独立,哈希函数也不同),首先决定哈希到哪个布隆过滤器,再在对应的布隆过滤器中使用对应的哈希函数进行插入,该方法的空间利用率高且假阳率低,实现较复杂,且需要手动调整块大小和哈希函数,否则会因为某个小布隆过滤器负载不均衡导致假阳率增加
  • 动态左计数布隆过滤器:结合计数、阻塞的思想。将集合分为多个小布隆过滤器,并且每个块中的每个位置都会维护一个计数器。该方法比起计数布隆过滤器,空间利用率更高,但在分布式场景下和布计数器的开销也会严重增加
  • 商过滤器:将集合划分为多个桶,每个桶中保存一个元素和一个余数。对元素哈希得到一个整数值,整数值的高位为桶的下标,地位代表余数,通过对比对应下表的余数是否相同来判断元素存在,该方法的缺点在于需要使用额外的元数据来管理每个元素,桶数需要为 2 的幂次方

在所有变体中,应用最广泛、效果最好的是布谷鸟过滤器

布谷鸟过滤器
介绍

基于布谷鸟哈希算法实现的过滤器,存储了哈希值的布谷鸟哈希表

相比布隆过滤器的优点

  1. 支持新增和删除元素

  2. 更节省空间

    1. 哈希表跟家紧凑
    2. 在错误率小于 3% 的时候空间性能优于布隆过滤器
    3. 空间节省 40% 多
  3. 查询效率高

    1. 一次哈希
    2. 而布隆过滤要采用多种哈希函数进行多次哈希
原理

最简单的布谷鸟哈希结构为一维数组结构,会有两个 hash 算法将新来的元素映射到数组的两个位置。若两个位置中有一个位置为空,则将元素直接放进去,若两个位置都满了,就【鸠占鹊巢】随机踢走一个,然后自己霸占该位置

  1. 保存元素(位置都没有被占):新来元素 a 经过 hash 为(A2,B1)的位置,由于 A2 还没有元素 a,直接落入 A2
  2. 保存元素(其中一个位置被占):新来元素 b 的 hash 为(A2,B3),由于 A2 已经被 a 占了,所以 b 会落在 b3

  1. 保存元素(两个位置都占):新来元素 c 的 hash 为(A2,B3),它会随机将一个元素挤走,这里挤走了 a

  1. 被挤掉的元素重新找位置:a 会重新进行 hash,找到还未被占的 B1 位置

问题:若数组太拥挤,将导致连续踢了若干次还未停止,严重影响插入效率。布谷鸟哈希设置一个阈值,当连续占巢行为超出了某个阈值,就认为数组几乎满了,这时需要对它进行扩容

为了提高空间利用率,降低碰撞概率,布谷鸟过滤器在布谷鸟哈希上做了改进, 将其从一维扩展为二维(每个桶存储的元素从 1 个变为 n 个),且每个位置中只存储几个 bit 的指纹,而非完整的元素

每个桶中存储了 4 个 slot,只有当一个桶中的所有 slot 都被填满的时候,才会使用替换的策略。这里的桶结构使用了一个二维数组来表示

应用场景

布谷鸟过滤器适用于需要支持动态数据集的应用场景,特别是需要支持删除的情况,具体应用场景包括但不限于

  • 缓存系统:用于缓存热点数据,减少后端系统的负载
  • 数据库:在数据库中作为索引结构,提高查询效率
  • 网络路由:在网络设备中用于快速查找路由表
  • 恶意软件检测:快速检测已知的恶意软件签名
  • 分布式系统:一致性检查,确保数据的一致性
代码实现
package com.yingzi;import java.util.Random;public class cuckooFilter {static final int _MAXIMUM_CAPACITY _= 1 << 30;//最大的踢出次数private final int MAX_NUM_KICKS = 500;//桶的个数private int capacity;//存入元素个数private int size = 0;//存放桶的数组private Bucket[] buckets;private Random random;//构造函数public cuckooFilter(int capacity) {capacity = _tableSizeFor_(capacity);this.capacity = capacity;buckets = new Bucket[capacity];random = new Random();for (int i = 0; i < capacity; i++) {buckets[i] = new Bucket();}}/** 向布谷鸟过滤器中插入一个元素** 插入成功,返回true* 过滤器已满或插入数据为空,返回false*/public boolean insert(Object o) {if (o == null)return false;/**  当我们知道 f 和 i1,就可以直接算出 i2,同样如果我们知道 i2 和 f,也可以直接算出 i1 (对偶性)*  所以我们根本不需要知道当前的位置是 p1 还是 p2,*  只需要将当前的位置和 hash(o) 进行异或计算就可以得到对偶位置。*  而且只需要确保 hash(o) != 0 就可以确保 i1 != i2,*  如此就不会出现自己踢自己导致死循环的问题。*/byte f = fingerprint(o);int i1 = hash(o);int i2 = i1 ^ hash(f);if (buckets[i1].insert(f) || buckets[i2].insert(f)) {//有空位置size++;return true;//插入成功}//没有空位置,relocate再插入return relocateAndInsert(i1, i2, f);}_/**_
_     * 对插入的值进行校验,只有当未插入过该值时才会插入成功_
_     * 若过滤器中已经存在该值,会插入失败返回false_
_     */_
_    _public boolean insertUnique(Object o) {if (o == null || contains(o))return false;return insert(o);}_/**_
_     * 随机在两个位置挑选一个将其中的一个值标记为旧值,_
_     * 用新值覆盖旧值,旧值会在重复上面的步骤进行插入_
_     */_
_    _private boolean relocateAndInsert(int i1, int i2, byte f) {boolean flag = random.nextBoolean();int itemp = flag ? i1 : i2;for (int i = 0; i < MAX_NUM_KICKS; i++) {//在桶中随机找一个位置int position = random.nextInt(Bucket._BUCKET_SIZE_);//踢出f = buckets[itemp].swap(position, f);itemp = itemp ^ hash(f);if (buckets[itemp].insert(f)) {size++;return true;}}//超过最大踢出次数,插入失败return false;}_/**_
_     * 如果此过滤器包含对象的指纹,返回true_
_     */_
_    _public boolean contains(Object o) {if(o == null)return false;byte f = fingerprint(o);int i1 = hash(o);int i2 = i1 ^ hash(f);return buckets[i1].contains(f) || buckets[i2].contains(f);}_/**_
_     * 从布谷鸟过滤器中删除元素_
_     * 为了安全地删除,此元素之前必须被插入过_
_     */_
_    _public boolean delete(Object o) {if(o == null)return false;byte f = fingerprint(o);int i1 = hash(o);int i2 = i1 ^ hash(f);return buckets[i1].delete(f) || buckets[i2].delete(f);}_/**_
_     * 过滤器中元素个数_
_     */_
_    _public int size() {return size;}//过滤器是否为空public boolean isEmpty() {return size == 0;}//得到指纹private byte fingerprint(Object o) {int h = o.hashCode();h += ~(h << 15);h ^= (h >> 10);h += (h << 3);h ^= (h >> 6);h += ~(h << 11);h ^= (h >> 16);byte hash = (byte) h;if (hash == Bucket._NULL_FINGERPRINT_)hash = 40;return hash;}//哈希函数public int hash(Object key) {int h = key.hashCode();h -= (h << 6);h ^= (h >> 17);h -= (h << 9);h ^= (h << 4);h -= (h << 3);h ^= (h << 10);h ^= (h >> 15);return h & (capacity - 1);}//hashMap的源码 有一个tableSizeFor的方法,目的是将传进来的参数转变为2的n次方的数值static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= _MAXIMUM_CAPACITY_) ? _MAXIMUM_CAPACITY _: n + 1;}static class Bucket {public static final int _FINGERPINT_SIZE _= 1;//桶大小为4public static final int _BUCKET_SIZE _= 4;public static final byte _NULL_FINGERPRINT _= 0;private final byte[] fps = new byte[_BUCKET_SIZE_];//在桶中插入public boolean insert(byte fingerprint) {for (int i = 0; i < fps.length; i++) {if (fps[i] == _NULL_FINGERPRINT_) {fps[i] = fingerprint;return true;}}return false;}//在桶中删除public boolean delete(byte fingerprint) {for (int i = 0; i < fps.length; i++) {if (fps[i] == fingerprint) {fps[i] = _NULL_FINGERPRINT_;return true;}}return false;}//桶中是否含此指纹public boolean contains(byte fingerprint) {for (int i = 0; i < fps.length; i++) {if (fps[i] == fingerprint)return true;}return false;}public byte swap(int position, byte fingerprint) {byte tmpfg = fps[position];fps[position] = fingerprint;return tmpfg;}}public static void main(String args[]){cuckooFilter c=new cuckooFilter(100);c.insert("西游记");c.insert("水浒传");c.insert("三国演义");System._out_.println(c.contains("水浒传"));}
}

参考资料

高级数据结构与算法 | 布谷鸟过滤器(Cuckoo Filter):原理、实现、LSM Tree 优化

Redis–布谷鸟过滤器–使用/原理/实例

布谷鸟过滤器的简单 Java 实现

【大数据管理】Java 实现布谷鸟过滤器(CF)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无需配置光猫,使用网管交换机配合路由器的IPTV功能实现单线复用

一、背景 弱电箱和电视柜只预留了一根网线&#xff0c;路由器放在电视柜&#xff0c;想实现既可以上网又可以正常观看iptv&#xff0c;本文提供了一种方法。 二、准备工作 1、带iptv功能的路由器&#xff1b;2、水星sg105pro网管交换机&#xff1b;3、网线若干&#xff1b; …

深入理解SpringBoot中的SpringCache缓存技术

深入理解SpringBoot中的SpringCache缓存技术 引言 在现代应用开发中&#xff0c;缓存技术是提升系统性能的重要手段之一。SpringBoot提供了SpringCache作为缓存抽象层&#xff0c;简化了缓存的使用和管理。本文将深入探讨SpringCache的核心技术点及其在实际业务中的应用场景。…

2025认证杯数学建模A题思路+代码+模型:小行星轨迹预测

2025认证杯数学建模A题思路代码模型&#xff0c;详细内容见文末名片 近地小行星&#xff08; Near Earth Asteroids, NEAs &#xff09;是轨道相对接近地球的小行 星&#xff0c;它的正式定义为椭圆轨道的近日距不大于 1.3 天文单位&#xff08; AU &#xff09;的小行星。 …

LeetCode Hot100刷题——轮转数组

56. 轮转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: …

「Mac畅玩AIGC与多模态41」开发篇36 - 用 ArkTS 构建聚合搜索前端页面

一、概述 本篇基于上一节 Python 实现的双通道搜索服务&#xff08;聚合 SearxNG 本地知识库&#xff09;&#xff0c;构建一个完整的 HarmonyOS ArkTS 前端页面。用户可在输入框中输入关键词&#xff0c;实时查询本地服务 http://localhost:5001/search?q...&#xff0c;返…

开源鸿蒙北向源码开发: 5.0kit化相关sdk编译

5.0kit化可以在编译系统sdk时添加,将你的kit文件加入编译使得最终生成的sdk包含kits文件 修改编译脚本 修改build仓里面的构建脚本文件,添加kits目录脚本命令 社区的build脚本已经有kits编译功能了,只需要把你的kits目录新增的kit拷贝到社区仓interface仓了,和社区的都一起编…

题单:汉诺塔问题

题目描述 如下图所示&#xff0c;设有 nn 个大小不等的中空圆盘&#xff0c;按照从小到大的顺序叠套在立柱 A 上&#xff0c;另有两根立柱 B 和 C 。 现在要求把全部圆盘从 A 柱&#xff08;称为源柱&#xff09;移到 C 柱&#xff08;称为目标柱&#xff09;&#xff0c;移动…

(面试)TCP、UDP协议

TCP&#xff08;传输控制协议&#xff09;和UDP&#xff08;用户数据报协议&#xff09;是互联网核心的传输层协议&#xff0c;负责应用程序之间的数据传输。它们在设计目标、特性和适用场景上有显著差异&#xff1a; TCP&#xff1a;面向连接&#xff0c;可靠的&#xff0c;速…

uni-app小程序登录后…

前情 最近新接了一个全新项目&#xff0c;是类似商城的小程序项目&#xff0c;我负责从0开始搭建小程序&#xff0c;我选用的技术栈是uni-app技术栈&#xff0c;其中就有一个用户登录功能&#xff0c;小程序部分页面是需要登录才可以查看的&#xff0c;对于未登录的用户需要引…

通识:计算机网络基础知识

目录 计算机网络的基本组成 计算机网络的主要分类 计算机网络的功能 计算机网络的关键技术 IP地址简介 IP地址的版本 IP地址的分类 公有与私有IP地址 ​编辑 子网掩码 计算机网络基础 IPv4与IPv6对比分析 IP地址分类简化版 公有与私有IP地址 计算机网络是指将地理…

三层固定实体架构:高效实现图上的检索增强生成(RAG)

知识图谱正在成为跨各个领域组织和检索信息的强大工具。它们越来越多地与机器学习和自然语言处理技术相结合,以增强信息检索和推理能力。在本文中,我介绍了一种用于构建知识图谱的三层架构,结合了固定本体实体、文档片段和提取的命名实体。通过利用嵌入和余弦相似度,这种方…

ArcGIS Pro地块图斑顺序编号(手绘线顺序快速编号)-004

ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放_arcgis初学者使用视频-CSDN博客 4大遥感软件&#xff01;遥感影像解译&#xff01;ArcGISENVIErdaseCognition_遥感解译软件-CSDN博客 今天介绍一下在ArcGIS Pro地块图斑顺序编号&#xff08;手绘线顺序快速编号&am…

Vue百日学习计划Day21-23天详细计划-Gemini版

总目标: 在 Day 21-23 完成 Vue.js 的介绍学习、环境搭建&#xff0c;并成功运行第一个 Vue 3 项目&#xff0c;理解其基本结构。 Day 21: Vue.js 介绍与概念理解 (~3 小时) 本日目标: 理解 Vue.js 是什么、渐进式框架的概念以及选择 Vue 的原因。初步了解 Vite 是什么及其作用…

uniapp-商城-60-后台 新增商品(属性的选中和页面显示,数组join 的使用)

前面添加了属性&#xff0c;添加属性的子级项目。也分析了如何回显&#xff0c;但是在添加新的商品的时&#xff0c;我们也同样需要进行选择&#xff0c;还要能正常的显示在界面上。下面对页面的显示进行分析。 1、界面情况回顾 属性显示其实是个一嵌套的数据显示。 2、选中的…

Vue框架

Vue 概况&#xff1a; Vue是一款用于构建用户界面的渐进式的JavaScript框架。&#xff08;官方;https:://cn.vuejs.org/) 框架:就是一套完整的项目解决方案&#xff0c;用于快速构建项目。 优点:大大提升前端项目的开发效率。 缺点:需要理解记忆框架的使用规则。&#xff…

解读RTOS 第七篇 · 驱动框架与中间件集成

1. 引言 在面向生产环境的 RTOS 系统中,硬件驱动框架与中间件层是连接底层外设与上层应用的桥梁。一个模块化、可扩展的驱动框架能够简化外设管理,提升代码可维护性;而丰富的中间件生态则为网络通信、文件系统、图形界面、安全加密等功能提供开箱即用的支持。本章将从驱动模…

JavaScript防抖与节流全解析

文章目录 前言:为什么需要防抖和节流基本概念与区别防抖(Debounce)节流(Throttle)关键区别防抖(Debounce)详解1. 基本防抖函数实现2. 防抖函数的使用3. 防抖函数的工作流程4. 防抖函数进阶 - 立即执行选项节流(Throttle)详解1. 基本节流函数实现时间戳法(第一次会立即执行)定…

JavaScript入门【3】面向对象

1.对象: 1.概述: 在js中除了5中基本类型之外,剩下得都是对象Object类型(引用类型),他们的顶级父类是Object;2.形式: 在js中,对象类型的格式为key-value形式,key表示属性,value表示属性的值3.创建对象的方式: 方式1:通过new关键字创建(不常用) let person new Object();// 添…

oracle主备切换参考

主备正常切换操作参考&#xff1a;RAC两节点->单机 &#xff08;rac和单机的操作区别&#xff1a;就是关闭其它节点&#xff0c;剩一个节点操作即可&#xff09; 1.主库准备 检查状态 SQL> select inst_id,database_role,OPEN_MODE from gv$database; INST_ID DATA…

端到端自动驾驶系统实战指南:从Comma.ai架构到PyTorch部署

引言&#xff1a;端到端自动驾驶的技术革命 在自动驾驶技术演进历程中&#xff0c;端到端&#xff08;End-to-End&#xff09;架构正引领新一轮技术革命。不同于传统分模块处理感知、规划、控制的方案&#xff0c;端到端系统通过深度神经网络直接建立传感器原始数据到车辆控制…