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

news2025/1/16 17:52:50

实现布谷鸟过滤器,每当有一个小说被存储后将其加入布谷鸟过滤器,并能够使用布谷鸟过滤器查询上述小说是否已经被存储

一、解题思路

在介绍布谷鸟过滤器之前,首先需要了解布谷鸟哈希的结构。最简单的布谷鸟哈希结构是一维数组结构,会有两个hash算法将新来的元素映射到数组的两个位置,如果两个位置中有一个位置为空,那么就可以将元素直接放进去;但是如果这两个位置都满了,它就不得不随机踢走一个,然后自己霸占这个位置。被踢走的元素首先会看看自己的另一个位置有没有空,如果空了,自己就挪过去;但是如果这个位置也被别的元素占据,它就会再将受害者的角色转嫁给的元素,然后这个新的受害者还会重复这个过程直到所有的元素都找到了自己的位置为止。当然这种循环不可能一直无限重复下去,所以要设置一个最大踢出次数MAX_NUM_KICKS,如果超过这个次数仍没有找到插入位置,就认为过滤器已满,插入失败。

布谷鸟过滤器和布谷鸟哈希结构一样,它也是一维数组,但是不同于布谷鸟哈希的是,布谷鸟哈希会存储整个元素,而布谷鸟过滤器中只会存储元素的指纹信息(几个bit,类似于布隆过滤器)。这里过滤器牺牲了数据的精确性换取了空间效率。正是因为存储的是元素的指纹信息,所以会存在误判率,这点和布隆过滤器如出一辙。首先布谷鸟过滤器还是只会选用两个 hash 函数,但是每个位置可以放置多个座位。这两个 hash 函数选择的比较特殊,因为过滤器中只能存储指纹信息。当这个位置上的指纹被挤兑之后,它需要计算出另一个对偶位置,而计算这个对偶位置是需要元素本身的。布谷鸟过滤器巧妙地设计了一个独特的hash 函数,使得可以根据p1和元素指纹直接计算出p2,而不需要完整的x元素。

byte f = fingerprint(o);
int i1 = hash(o);
int i2 = i1 ^ hash(f);
当我们知道fi1,就可以直接算出i2,同样地如果我们知道i2f,也可以直接算出i1 (对偶性),所以我们根本不需要知道当前的位置是i1还是i2,只需要将当前的位置和hash(o) 进行异或计算就可以得到对偶位置,而且只需要确保 hash(o) != 0 就可以确保 p1 != p2,如此就不会出现自己踢自己导致死循环的问题。因为采用了这种候选桶与首选桶可以通过位置和存储值互相异或得出的备用候选桶的方案,所以这种对应关系要求桶的大小必须是2的幂次方。
下面对cuckooFilter类的主要方法进行介绍:
insert方法用于向布谷鸟过滤器中插入一个元素o,如果o为空,则插入失败;否则如果桶中有空位置就直接插入,否则调用relocateAndInsert方法。
public boolean insert(Object o) {
    if (o == null)
        return false;
    
    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);
}
relocateAndInsert方法随机在两个位置挑选一个将其中的一个值标记为旧值,用新值覆盖旧值,旧值会在重复上面的步骤进行插入。Flag是一个随机的boolean类型变量,如果为true就把i1踢出,如果为false就把i2踢出,并把被提出的元素赋值给itemp;之后进行最多为最大踢出次数的循环,在桶中随机找一个位置position,将新的指纹放入position,插入元素个数size自增,同时这一轮的受害者进入下一轮寻找新的受害者踢出。
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;
}

完整代码:

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;
        //桶大小为4
        public 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("水浒传"));

    }
}

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

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

相关文章

JAVA基础知识05面向对象

目录 面向对象概述 为什么要学习面向对象&#xff1f; 1. 类和对象 1.1 类的介绍 1.2 类和对象的关系 组织代码 1.3 类的组成 1.4 创建对象和使用对象的格式 2. 对象内存图 2.1 单个对象内存图 2.2 两个对象内存图 3. 成员变量和局部变量 4. this 关键字 4.1 t…

【c语言进阶】结构体最常用知识点大全

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我…

【电动车】基于多目标优化遗传算法NSGAII的峰谷分时电价引导下的电动汽车充电负荷优化研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

使用Redission和Aop以及注解实现接口幂等性

关于什么是接口幂等性这里不再赘述&#xff0c;本文将使用分布式锁来解决接口幂等性的问题。 本文接口幂等判断标准&#xff1a; String name IP 请求方式 URI 参数摘要值 当相同的name来临时&#xff0c;且上一个相同name对于的接口还未正常执行完毕&#xff0c;则判断为…

Python ·信用卡欺诈检测【Catboost】

Python 信用卡欺诈检测【Catboost】 提示&#xff1a;前言 Python 信用卡欺诈检测 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录Python 信用卡欺诈检测【Catboost】前言一、导入包二、加载数据三、数据可视化四、…

鱼雷的发射角设置

过年嘛 放松个五六天啥的 玩了个猎杀潜航的游戏觉得那玩意挺有意思的开年了 要美赛 写个设置鱼雷发射角的小程序玩玩 游戏嘛,反正大概简易版就是这个框架,自己补充呗 各种设定啥的,没怎么关心,就是总结一下里面的平面几何..水个文章玩玩顺便练习一下pptx绘图美赛的时候估计还是…

30. PyQuery: 基于HTML的CSS选择器

目录 前言 导包 基本用法 按标签选择 标签链式操作 简便链式&#xff1a;后代选择器 类选择器 id 选择器 属性/文本选择器&#xff08;重点&#xff09; 改进多标签拿属性方法 快速总结 PyQuery的强大功能&#xff1a;修改源代码 添加代码块 修改/添加属性 删…

java spring IOC xml方式注入(数组 list集合 map集合 set集合)类型属性

我们先创建一个基本的java项目 然后引入 spring 的基本依赖 然后在src下创建一个包 我这里叫 collectiontype 和我同名 会避免一些找不到资源的麻烦 毕竟说 你们开发代码大部分会在这篇文章拿过去 当然 名称是看自己去取的 只是和我同名会方便一些 直接复制过去就好了 然后在…

C语言函数定义

函数是一段可以重复使用的代码&#xff0c;用来独立地完成某个功能&#xff0c;它可以接收用户传递的数据&#xff0c;也可以不接收。接收用户数据的函数在定义时要指明参数&#xff0c;不接收用户数据的不需要指明&#xff0c;根据这一点可以将函数分为有参函数和无参函数。将…

ARP渗透与攻防(七)之Ettercap Dns劫持

系列文章 ARP渗透与攻防(一)之ARP原理 ARP渗透与攻防(二)之断网攻击 ARP渗透与攻防(三)之流量分析 ARP渗透与攻防(四)之WireShark截获用户数据 ARP渗透与攻防(五)之Ettercap劫持用户流量 ARP渗透与攻防(六)之限制网速攻击 ARP-Ettercap Dns劫持 1.什么是DNS 1.概念 DNS是D…

汇编语言学习 下

本文承接汇编语言学习笔记 上 上篇文章记录了汇编语言寄存器&#xff0c;汇编语言基本组成部分&#xff0c;数据传送指令&#xff0c;寻址指令&#xff0c;加减法指令&#xff0c;堆栈&#xff0c;过程&#xff0c;条件处理&#xff0c;整数运算的内容 高级过程 大多数现代编程…

通信原理简明教程 | 信号、信道与噪声

文章目录1 信号与系统的基本概念2 傅里叶变换与信号的频谱2.1 信号的频谱2.2 傅里叶变换的常用性质2.3 信号的能量谱、功率谱与自相关函数2.4 信号的带宽3 线性系统与滤波器3.1 线性系统及其频率特性3.2 系统响应的频域求解3.3 滤波器2.4 信道及其特性4.1 信道的分类4.2 信道的…

兔年伊始谈闲书:三体-乌合之众-百年孤独 和《猫城记》(随笔)

引言 “ 各位读者&#xff0c;我们都兔年大吉&#xff0c;新春快乐。我本想写一篇《公有云换帅转舵后的错误经验和正经歪念》&#xff0c;但刚过年就炮火味十足&#xff0c;觉得有失体面。所以就写了篇读书的散记杂记。本文和云计算行业完全无关&#xff0c;就是一个有常识性阅…

[数据结构基础]排序算法第一弹 -- 直接插入排序和希尔排序

一. 排序的概念及分类 1.1 排序的概念 排序&#xff0c;就是使一串数据&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 1.2 常见的排序算法 图1.1按照排序算法的思想&#xff0c;将排序分为四大类&#xff1a;插入排序、选择排序…

OpenGL相关库及其关系概述

目录 1、OpenGL 2. .GLUT 3、Freeglut 4、glew 5、glfw 6、glad 1、OpenGL OpenGL只有框架没有实现&#xff0c;换句话说就是OpenGL只有函数声明没有源文件实现&#xff0c;类似于接口和虚函数。所有的实现是显卡生产商提供。比如NVIDIA或者AMD就要自己实现OpenGL函数内容…

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

文章目录一、项目环境二、生成公钥私钥1.生成私钥2.查看私钥3.生成公钥4.查看公钥三、安装依赖包四、自测加解密1.纯前端自测2.前后端联调①前端登录代码改为&#xff1a;②后端登录接口代码&#xff1a;③验证五、与数据库密码进行对比&#xff08;数据库是进行了bcryptjs加密…

关于非授权访问的逻辑漏洞挖掘

简介 挖洞的时候日常笔记&#xff0c;很多细节都写得不好&#xff0c;师傅们不要介意 开始 获取目标&#xff1a;https://fofa.info/ 在fofa上输入body“后台登录”&#xff0c;可以看到一大堆目标&#xff0c;今天我打算去测试逻辑漏洞 进入目标地址 随意输入用户名和密码&…

Python LC Loan贷款数据集 统计分析 数据挖掘 研究报告

实验代码&#xff1a;https://download.csdn.net/download/Amzmks/87396462 首先读表 将有空值的列和完全相同的列删除 将数值型数据单独挑出来 将数值型数据从string转为float 用方差阈值法筛选特征较为明显的部分数值型数据 将文本型数据单独挑出来 去除所有的可能的头…

计算机存储系统

前言何为存储系统&#xff1f;存储系统是指计算机中由存放程序和数据的各种存储设备、控制部件及管理信息调度的设备&#xff08;硬件&#xff09;和算法&#xff08;软件&#xff09;所组成的系统。为何需要存储系统&#xff1f;信息是人类认知外界的方式&#xff0c;最初的信…

对CAS的理解

CAS的概念&#xff08;compare and swap&#xff09;:比较并交换我们首先要明白&#xff0c;自旋是一个纯用户态的操作&#xff0c;本身并没有进行加锁&#xff0c;所执行的代码&#xff0c;是在并发环境下执行的&#xff0c;没有锁。我们举一个例子来说明自旋加CAS的作用&…