我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用golang怎么实现一个布谷鸟过滤器

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用golang怎么实现一个布谷鸟过滤器

本文章向大家介绍使用golang怎么实现一个布谷鸟过滤器的基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

golang适合做什么

golang可以做服务器端开发,但golang很适合做日志处理、数据打包、虚拟机处理、数据库代理等工作。在网络编程方面,它还广泛应用于web应用、API应用等领域。

实现原理

简单工作原理

可以简单的把布谷鸟过滤器里面有两个 hash 表T1、T2,两个 hash 表对应两个 hash 函数H1、H2。

具体的插入步骤如下:

  • 当一个不存在的元素插入的时候,会先根据 H1 计算出其在 T1 表的位置,如果该位置为空则可以放进去。

  • 如果该位置不为空,则根据 H2 计算出其在 T2 表的位置,如果该位置为空则可以放进去。

  • 如果T1 表和 T2 表的位置元素都不为空,那么就随机的选择一个 hash 表将其元素踢出。

  • 被踢出的元素会循环的去找自己的另一个位置,如果被暂了也会随机选择一个将其踢出,被踢出的元素又会循环找位置;

  • 如果出现循环踢出导致放不进元素的情况,那么会设置一个阈值,超出了某个阈值,就认为这个 hash 表已经几乎满了,这时候就需要对它进行扩容,重新放置所有元素。

下面举一个例子来说明:

使用golang怎么实现一个布谷鸟过滤器

如果想要插入一个元素Z到过滤器里:

  • 首先会将Z进行 hash 计算,发现 T1 和 T2 对应的槽位1和槽位2都已经被占了;

  • 随机将 T1 中的槽位1中的元素 X 踢出,X 的 T2 对应的槽位4已经被元素 3 占了;

  • 将 T2 中的槽位4中的元素 3 踢出,元素 3 在 hash 计算之后发现 T1 的槽位6是空的,那么将元素3放入到 T1 的槽位6中。

当 Z 插入完毕之后如下:

使用golang怎么实现一个布谷鸟过滤器

布谷鸟过滤器

布谷鸟过滤器和上面的实现原理结构是差不多的,不同的是上面的数组结构会存储整个元素,而布谷鸟过滤器中只会存储元素的几个 bit ,称作指纹信息。这里是牺牲了数据的精确性换取了空间效率。

上面的实现方案中,hash 表中每个槽位只能存放一个元素,空间利用率只有50%,而在布谷鸟过滤器中每个槽位可以存放多个元素,从一维变成了二维。论文中表示:

With k = 2 hash functions, the load factor α is 50% when the bucket size b = 1 (i.e., the hash table is directly mapped), but increases to 84%, 95% or 98% respectively using bucket size b = 2, 4 or 8.

也就是当有两个 hash 函数的时候,使用一维数组空间利用率只有50%,当每个槽位可以存放2,4,8个元素的时候,空间利用率就会飙升到 84%,95%,98%。

如下图,表示的是一个二维数组,每个槽位可以存放 4 个元素,和上面的实现有所不同的是,没有采用两个数组来存放,而是只用了一个:

使用golang怎么实现一个布谷鸟过滤器

说完了数据结构的改变,下面再说说位置计算的改变。

我们在上面简单实现的位置计算公式是这样做的:

p1 = hash2(x) % 数组长度p2 = hash3(x) % 数组长度

而布谷鸟过滤器计算位置公式可以在论文中看到是这样:

f = fingerprint(x);i1 = hash(x);i2 = i1 ⊕ hash( f);

我们可以看到在计算位置 i2 的时候是通过 i1 和元素 X 对应的指纹信息取异或计算出来。指纹信息在上面已经解释过了,是元素 X 的几个 bit ,牺牲了一定精度,但是换取了空间。

那么这里为什么需要用到异或呢?因为这样可以用到异或的自反性: A ⊕ B ⊕ B = A ,这样就不需要知道当前的位置是 i1 还是 i2,只需要将当前的位置和 hash(f) 进行异或计算就可以得到另一个位置。

这里有个细节需要注意的是,计算 i2 的时候是需要先将元素 X 的 fingerprint 进行 hash ,然后才取异或,论文也说明了:

If the alternate location were calculated by “i⊕fingerprint” without hashing the fingerprint, the items kicked out from nearby buckets would land close to each other in the table, if the size of the fingerprint is small compared to the table size.

如果直接进行异或处理,那么很可能 i1 和 i2 的位置相隔很近,尤其是在比较小的 hash 表中,这样无形之中增加了碰撞的概率。

除此之外还有一个约束条件是布谷鸟过滤器强制数组的长度必须是 2 的指数,所以在布谷鸟过滤器中不需要对数组的长度取模,取而代之的是取 hash 值的最后 n 位。

如一个布谷鸟过滤器中数组的长度2^8即256,那么取 hash 值的最后 n 位即: hash & 255 这样就可以得到最终的位置信息。如下最后得到位置信息是 23 :

使用golang怎么实现一个布谷鸟过滤器

代码实现

数据结构

const bucketSize = 4type fingerprint byte// 二维数组,大小是4type bucket [bucketSize]fingerprinttype Filter struct {// 一维数组buckets []bucket// Filter 中已插入的元素count  uint// 数组buckets长度中对应二进制包含0的个数bucketPow uint}

在这里我们假定一个指纹 fingerprint 占用的字节数是 1byte ,每个位置有 4 个座位。

初始化

var (altHash = [256]uint{}masks = [65]uint{})func init() {for i := 0; i < 256; i++ {  // 用于缓存 256 个fingerprint的hash信息altHash[i] = (uint(metro.Hash74([]byte{byte(i)}, 1337)))}for i := uint(0); i <= 64; i++ {  // 取 hash 值的最后 n 位masks[i] = (1 << i) - 1}}

这个 init 函数会缓存初始化两个全局变量 altHash 和 masks。因为 fingerprint 长度是 1byte ,所以在初始化 altHash 的时候使用一个 256 大小的数组取缓存对应的 hash 信息,避免每次都需要重新计算;masks 是用来取 hash 值的最后 n 位,稍后会用到。

我们会使用一个 NewFilter 函数,通过传入过滤器可容纳大小来获取过滤器 Filter:

func NewFilter(capacity uint) *Filter { // 计算 buckets 数组大小capacity = getNextPow2(uint64(capacity)) / bucketSizeif capacity == 0 {capacity = 1}buckets := make([]bucket, capacity)return &Filter{buckets: buckets,count:  0,  // 获取 buckets 数组大小的二进制中以 0 结尾的个数bucketPow: uint(bits.TrailingZeros(capacity)),}}

NewFilter 函数会通过 getNextPow2 将 capacity 调整到 2 的指数倍,如果传入的 capacity 是 9 ,那么调用 getNextPow2 后会返回 16;然后计算好 buckets 数组长度,实例化 Filter 返回;bucketPow 返回的是二进制中以 0 结尾的个数,因为 capacity 是 2 的指数倍,所以 bucketPow 是 capacity 二进制的位数减 1。

插入元素

func (cf *Filter) Insert(data []byte) bool {// 获取 data 的 fingerprint 以及 位置 i1i1, fp := getIndexAndFingerprint(data, cf.bucketPow)// 将 fingerprint 插入到 Filter 的 buckets 数组中if cf.insert(fp, i1) {return true}// 获取位置 i2i2 := getAltIndex(fp, i1, cf.bucketPow)// 将 fingerprint 插入到 Filter 的 buckets 数组中if cf.insert(fp, i2) {return true}// 插入失败,那么进行循环插入踢出元素return cf.reinsert(fp, randi(i1, i2))}func (cf *Filter) insert(fp fingerprint, i uint) bool { // 获取 buckets 中的槽位进行插入if cf.buckets[i].insert(fp) {  // Filter 中元素个数+1cf.count++return true}return false}func (b *bucket) insert(fp fingerprint) bool { // 遍历槽位的 4 个元素,如果为空则插入for i, tfp := range b {if tfp == nullFp {b[i] = fpreturn true}}return false}
  • getIndexAndFingerprint 函数会获取 data 的指纹 fingerprint,以及位置 i1;

  • 然后调用 insert 插入到 Filter 的 buckets 数组中,如果 buckets 数组中对应的槽位 i1 的 4 个元素已经满了,那么尝试获取位置 i2 ,并将元素尝试插入到 buckets 数组中对应的槽位 i2 中;

  • 对应的槽位 i2 也满了,那么 调用 reinsert 方法随机获取槽位 i1、i2 中的某个位置进行抢占,然后将老元素踢出并循环重复插入。

下面看看 getIndexAndFingerprint 是如何获取 fingerprint 以及槽位 i1:

func getIndexAndFingerprint(data []byte, bucketPow uint) (uint, fingerprint) { // 将 data 进行hashhash := metro.Hash74(data, 1337) // 取 hash 的指纹信息fp := getFingerprint(hash)// 取 hash 高32位,对 hash 的高32位进行取与获取槽位 i1i1 := uint(hash>>32) & masks[bucketPow]return i1, fingerprint(fp)}// 取 hash 的指纹信息func getFingerprint(hash uint64) byte {fp := byte(hash%255 + 1)return fp}

getIndexAndFingerprint 中对 data 进行 hash 完后会对其结果取模获取指纹信息,然后再取 hash 值的高 32 位进行取与,获取槽位 i1。masks 在初始化的时候已经看过了, masks[bucketPow] 获取的二进制结果全是 1 ,用来取 hash 的低位的值。

假如初始化传入的 capacity 是1024,那么计算到 bucketPow 是 8,对应取到 masks[8] = (1 << 8) - 1 结果是 255 ,二进制是 1111,1111 ,和 hash 的高 32 取与 得到最后 buckets 中的槽位 i1 :

使用golang怎么实现一个布谷鸟过滤器

func getAltIndex(fp fingerprint, i uint, bucketPow uint) uint {mask := masks[bucketPow]hash := altHash[fp] & maskreturn i ^ hash}

getAltIndex 中获取槽位是通过使用 altHash 来获取指纹信息的 hash 值,然后取异或后返回槽位值。需要注意的是,这里由于异或的特性,所以传入的不管是槽位 i1,还是槽位 i2 都可以返回对应的另一个槽位。

下面看看循环踢出插入 reinsert:

const maxCuckooCount = 500func (cf *Filter) reinsert(fp fingerprint, i uint) bool { // 默认循环 500 次for k := 0; k < maxCuckooCount; k++ {  // 随机从槽位中选取一个元素j := rand.Intn(bucketSize)oldfp := fp  // 获取槽位中的值 fp = cf.buckets[i][j]  // 将当前循环的值插入cf.buckets[i][j] = oldfp// 获取另一个槽位i = getAltIndex(fp, i, cf.bucketPow)if cf.insert(fp, i) {return true}}return false}

这里会最大循环 500 次获取槽位信息。因为每个槽位最多可以存放 4 个元素,所以使用 rand 随机从 4 个位置中取一个元素踢出,然后将当次循环的元素插入,再获取被踢出元素的另一个槽位信息,再调用 insert 进行插入。

使用golang怎么实现一个布谷鸟过滤器

上图展示了元素 X 在插入到 hash 表的时候,hash 两次发现对应的槽位 0 和 3 都已经满了,那么随机抢占了槽位 3 其中一个元素,被抢占的元素重新 hash 之后插入到槽位 5 的第三个位置上。

查询数据

查询数据的时候,就是看看对应的位置上有没有对应的指纹信息:

func (cf *Filter) Lookup(data []byte) bool { // 获取槽位 i1 以及指纹信息i1, fp := getIndexAndFingerprint(data, cf.bucketPow) // 遍历槽位中 4 个位置,查看有没有相同元素if cf.buckets[i1].getFingerprintIndex(fp) > -1 {return true} // 获取另一个槽位 i2i2 := getAltIndex(fp, i1, cf.bucketPow) // 遍历槽位 i2 中 4 个位置,查看有没有相同元素return cf.buckets[i2].getFingerprintIndex(fp) > -1}func (b *bucket) getFingerprintIndex(fp fingerprint) int {for i, tfp := range b {if tfp == fp {return i}}return -1}

删除数据

删除数据的时候,也只是抹掉该槽位上的指纹信息:

func (cf *Filter) Delete(data []byte) bool { // 获取槽位 i1 以及指纹信息i1, fp := getIndexAndFingerprint(data, cf.bucketPow) // 尝试删除指纹信息if cf.delete(fp, i1) {return true} // 获取槽位 i2i2 := getAltIndex(fp, i1, cf.bucketPow) // 尝试删除指纹信息return cf.delete(fp, i2)}func (cf *Filter) delete(fp fingerprint, i uint) bool { // 遍历槽位 4个元素,尝试删除指纹信息if cf.buckets[i].delete(fp) {if cf.count > 0 {cf.count--}return true}return false}func (b *bucket) delete(fp fingerprint) bool {for i, tfp := range b {  // 指纹信息相同,将此槽位置空if tfp == fp {b[i] = nullFpreturn true}}return false}

缺点

实现完布谷鸟过滤器后,我们不妨想一下,如果布谷鸟过滤器对同一个元素进行多次连续的插入会怎样?

那么这个元素会霸占两个槽位上的所有位置,最后在插入第 9 个相同元素的时候,会一直循环挤兑,直到最大循环次数,然后返回一个 false:

使用golang怎么实现一个布谷鸟过滤器

如果插入之前做一次检查能不能解决问题呢?这样确实不会出现循环挤兑的情况,但是会出现一定概率的误判情况。

由上面的实现我们可以知道,在每个位置里设置的指纹信息是 1byte,256 种可能,如果两个元素的 hash 位置相同,指纹相同,那么这个插入检查会认为它们是相等的导致认为元素已存在。

事实上,我们可以通过调整指纹信息的保存量来降低误判情况,如在上面的实现中,指纹信息是 1byte 保存8位信息误判概率是0.03,当指纹信息增加到 2bytes 保存16位信息误判概率会降低至 0.0001。

以上就是小编为大家带来的使用golang怎么实现一个布谷鸟过滤器的全部内容了,希望大家多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用golang怎么实现一个布谷鸟过滤器

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用golang怎么实现一个布谷鸟过滤器

本文章向大家介绍使用golang怎么实现一个布谷鸟过滤器的基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。golang适合做什么golang可以做服务器端开发,但golang很适合做日志处理、数据打包、虚拟机处理、
2023-06-06

怎么实现一个更全面的Golang版本的布谷鸟过滤器

这篇文章给大家分享的是有关怎么实现一个更全面的Golang版本的布谷鸟过滤器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。布谷鸟过滤器布谷鸟过滤器在网络上已经有很多的介绍文章了,这里不再做过多的介绍,只提一下要点
2023-06-08

使用canvas怎么实现一个滤镜功能

使用canvas怎么实现一个滤镜功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1 了解 canvas?1.1 什么是 canvas?这个 HTML 元素是为
2023-06-09

使用golang怎么实现一个DNS服务器

使用golang怎么实现一个DNS服务器?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。golang适合做什么golang可以做服务器端开发,但golang很适合做日志处理、
2023-06-14

使用CSS3怎么实现一个瀑布流布局

使用CSS3怎么实现一个瀑布流布局?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。掌握点:1、column-count 把div中的文本分为多少列2、column-width 规
2023-06-08

使用css怎么实现一个n宫格布局

使用css怎么实现一个n宫格布局?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。设计目标在scss环境下, 通过mixin实现n宫格, 并且可以支持"有无边框"和"每个格是否正方
2023-06-08

使用golang怎么实现一个京东支付功能

这篇文章主要介绍了使用golang怎么实现一个京东支付功能,此处通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考价值,需要的朋友可以参考下:什么是golanggolang 是Google开发的一种静态强类型、编译型、并发型
2023-06-06

使用CSS怎么实现一个响应式布局系统

这篇文章将为大家详细讲解有关使用CSS怎么实现一个响应式布局系统,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。什么是csscss是一种用来表现HTML或XML等文件样式的计算机语言,主要是用
2023-06-08

Golang怎么使用channel实现一个优雅退出功能

这篇文章主要介绍了Golang怎么使用channel实现一个优雅退出功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang怎么使用channel实现一个优雅退出功能文章都会有所收获,下面我们一起来看看吧
2023-07-05

使用golang怎么实现一个登录验证码功能

这篇文章将为大家详细讲解有关使用golang怎么实现一个登录验证码功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。golang适合做什么golang可以做服务器端开发,但golang很适合
2023-06-06

使用golang怎么实现一个比特币交易功能

使用golang怎么实现一个比特币交易功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。比特币交易交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正
2023-06-15

使用JavaScript怎么实现一个计算器

这期内容当中小编将会给大家带来有关使用JavaScript怎么实现一个计算器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。具体内容如下
2023-06-14

使用python怎么实现一个定时器

这期内容当中小编将会给大家带来有关使用python怎么实现一个定时器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚
2023-06-14

使用Java怎么实现一个Iterator迭代器

这期内容当中小编将会给大家带来有关使用Java怎么实现一个Iterator迭代器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。package me.socketthread;import java.uti
2023-05-30

怎么使用C#实现一个PPT遥控器

这篇文章给大家分享的是有关怎么使用C#实现一个PPT遥控器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。说明本项目参考了 https://github.com/yangzhongke/PhoneAsPrompte
2023-06-15

使用springmvc怎么实现一个限流拦截器

这期内容当中小编将会给大家带来有关使用springmvc怎么实现一个限流拦截器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。限流器算法目前常用限流器算法为两种:令牌桶算法和漏桶算法,主要区别在于:漏桶算法
2023-05-30

使用CocosCreator怎么实现一个计时器功能

这篇文章给大家介绍使用CocosCreator怎么实现一个计时器功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、setTimeOut3秒后打印abc。只执行一次。setTimeout(()=>{console.l
2023-06-14

怎么在HTML5中使用canvas实现一个瀑布流文字效果

今天就跟大家聊聊有关怎么在HTML5中使用canvas实现一个瀑布流文字效果,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。代码如下:
2023-06-09

使用javaCV怎么实现一个推流器和录制器

这篇文章给大家介绍使用javaCV怎么实现一个推流器和录制器,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。功能实现边播放边录制/推流,停止预览即停止录制/推流开发所依赖的包javacv.jar,javacpp.jar,
2023-06-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录