过滤器系列(二)——布谷过滤器

这一篇讲的是布谷过滤器(cuckoo fliter),这个名字来源于更早发表的布谷散列(cuckoo hash),尽管我也不知道为什么当初要给这种散列表起个鸟名=_=

由于布谷过滤器本身的思想就源自于布谷散列,那么我们就从布谷散列开始说它的设计思想。产生布谷散列表的一个重要背景是人们对于球盒问题的分析:给定N个球,随机的放在N个盒子里,在装球最多的盒子里,球的个数的期望是多少?答案是\(\Theta (logN/loglogN)\),这个问题其实就是散列表装填因子为1时的情况分析。后来有一天,人们发现:每次放球的时候,如果随机选择两个盒子,将球放到当时较空的那个盒子里,那么这个期望就变成了\(\Theta (loglogN)\),这个界小于之前的界,这给了布谷散列表作者启发。

一个布谷散列表通常有两张(一般来说)表,分别有一个对应的Hash函数,当有新的项插入的时候,它会计算出这个项在两张表中对应的两个位置,这个项一定会被存在这两个位置之一,而具体是哪一个却不确定。

下图是一个布谷散列表的初始化示意图:

现在我们假设有一些项要存入散列表,其每个项都有其对应的两个位置,先插入第一项A

由于插入A的时候其两个候选位置(0,2)都没有占用,所以选择第一张表或者是第二张表都可以,我们在这里默认先选择第一张表,然后插入第二项B

我们看到原来的A的位置被B占用,而A被“踢”到它的备选位置表二的2号位置上了,这就是当发生位置冲突时,布谷散列表的处理逻辑,后来的数据项将会把之前占用的项踢到另一个位置上。我们接下来插入第三项C


没有冲突,顺利搞定,接着插入D


D成功的把C踢走了,其实看到这里读者应该在猜想,会不会有一种情况,即被踢走的数据的另一个备选位置也被占用了,这样怎么办?答案是继续踢,一个踢一个,直到大家都找到自己合适的归宿为止。那么如果发现出现了循环怎么办?答案是GG,这代表布谷散列表走到了极限


插入E


这里就发生了多次替换的情况,F代替了E,E代替了A,A代替了B,B找到了空余的位子

读者可以考虑一下,如果这个时候要想插入一个“G”,其备选位置是1,2,这样的话会出现什么情况?

好了,布谷散列表大概介绍完了,现在该布谷过滤器了。布谷过滤器的主要也是通过布谷散列来实现的,其主要变化是:
1.我们不将原来的数据完整的存进来,作为过滤器,当然要省点空间,选用的办法设计一个指纹,将比较大的原数据变成了一个个指纹串,这样就大大节省了空间。
2.由于第一点所说,存储的不是原数据,那么在替换位置的时候,我们需要再次计算原来的数据的备选位置,这样一来布谷散列表的方法就失效了。在这里,作者设计了一个方法,他将两个Hash函数变成了一个Hash函数,第一张表的备选位置是Hash(x),第二张表的备选位置是\(Hash(x) \oplus hash(fingerprint(x))\),即第一张表的位置与存储的指纹的Hash值做异或运算。这样做的优点就是不用再另外存储元素的备选位置,在替换时,可以直接用异或来计算出其另一个备选位置。(读者可以想想如何通过表二的位置计算出元素在表一中的位置)
3.插入时,优先选择空位置,而不是尽可能的踢走其他元素。

插入伪代码如下:

Algorithm 1: Insert(x)

f = fingerprint(x)
i1 = hash(x)
i2 = i1 xor hash(f)
if bucket[i1] or bucket[i2] has an empty entry then
    //只要有空位就先插入空位里
    add f to that bucket
    return Done
i  = randomly pick i1 or i2
for n=0;n<MaxNumKicks;++n
    randomly select an entry e from bucket[i];
    swap f and the fingerprint stored in entry e;
    i = i xor hash(f)
    if bucket[i] has an empty entry then
        add f to bucket[i]
        return Done
return Failure // 已经出现循环情况了

查找伪代码如下:

Algorithm 2: Lookup(x)
f = fingerprint(x)
i1 = hash(x)
i2 = i1 xor hash(f)
if bucket[i1] or bucket[i2] has f then
    return True
return False

删除伪代码如下:

Algorithm 3: Delete(x)

f = fingerprint(x)
i1 = hash(x)
i2 = i1 xor hash(f)
if bucket[i1] or bucket[i2] has f then
    remove a copy of f from this bucket
return False

删除这部分值得注意,当被删除元素的另一个备选位置有其他元素的指纹的时候,我们不能确定到底哪一个是要删除的元素,其实我们也不去关心到底是不是要删除的元素,我们直接删除掉其中的一个。这样一来,我们其实并没有真正的删除掉元素,为什么这么说,因为当你删除掉这个元素的时候我们再查找这个元素,按照算法来看我们还是一样能检索出来这个元素在我们的布谷过滤器里,这就是假阳率的其中一个来源(还有一个重要来源是指纹构造的重复,即多个元素产生相同指纹)

下面我们来分析一下布谷过滤器的平均每个元素占用的比特数,设每个桶里装\(b\)个指纹,要求错误率的上界为\(\epsilon\),\(f\)为指纹长度。

错误率的上界 = \(1-(1-1/2^f)^{2b} \approx 2b/2^f\)
那么这个上界要求小于要求的上界,即\(2b/2^f \le \epsilon\),得到
\(f \ge log_2^{2b/\epsilon} = log_2^{1/\epsilon} + log_2^{2b}\)
则平均每个元素占用的比特数为\(C \le (log_2^{1/\epsilon} + log_2^{2b}) / \alpha\)

在原论文中,作者其实后面还做了一个比较强行的优化,在此不提,后面设计其他过滤器的作者也没有把这个优化算数。。。。不过作者提到了在实际测试中,他们发现当b=4的时候是空间性能最好的情况,所以一般说来,我们直接把b当做常数4,代入到前面算出来的公式中,\(C \le (log_2^{1/\epsilon} + 3) / \alpha\)

布谷过滤器就说到这,布谷过滤器在错误率小于3%的时候空间性能是优于布隆过滤器的,而这个条件在实际应用中常常满足,所以一般来说它的空间性能是要优于布隆过滤器的。而且,布谷过滤器按照普通设计,只有两个桶,在查找的时候可以确保两次访存就可以做完,相比于布隆过滤器的K个Hash函数K次访存,在数据量很大不能全部装载在内存中的情况下,多一次访存那么时间上就输了。不过说完优点,布谷过滤器也有其相应的缺点,当装填因子较高的时候,容易出现循环的问题,即插入失败的情况,到这时就很难办。另外,它还有跟布隆过滤器共有的一个缺点,就是访问空间地址不连续,通常可以认为是随机的,这样严重破坏了程序局部性,对于Cache流水线来说非常不利,这为之后的过滤器设计埋下了一个伏笔。

原文地址:https://www.cnblogs.com/chuxiuhong/p/8215719.html

时间: 2024-10-15 22:38:50

过滤器系列(二)——布谷过滤器的相关文章

JavaWeb 后端 &lt;十二&gt; 之 过滤器 filter 乱码、不缓存、脏话、标记、自动登录、全站压缩过滤器

一.过滤器是什么?有什么? 1.过滤器属于Servlet规范,从2.3版本就开始有了. 2.过滤器就是对访问的内容进行筛选(拦截).利用过滤器对请求和响应进行过滤 二.编写步骤和执行过程 1.编码步骤: a.编写一个类:实现javax.servlet.Filter接口 public class FilterDemo1 implements Filter { public FilterDemo1(){ System.out.println("调用了默认的构造方法"); } //用户每次访

一、变量.二、过滤器(filter).三、标签(tag).四、条件分支tag.五、迭代器tag.六、自定义过滤器与标签.七、全系统过滤器(了解)

一.变量 ''' 1.视图函数可以通过两种方式将变量传递给模板页面 -- render(request, 'test_page.html', {'变量key1': '变量值1', ..., '变量keyn': '变量值n'}) -- render(request, 'test_page.html', locals()) # locals() 就是将视图函数中的所有变量都传递给模板页面 2.模板页面中对变量的使用 def dtl(request): num = 3.14 ss = '大碗宽面' #

MVC FILTER过滤器(二)

授权过滤器: /// <summary> /// 授权过滤器 /// </summary> public class TestAuthorizeAttribute:AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { filterContext.HttpContext.Response.Write("OnAuthorization<

Wireshark入门与进阶系列(二)

摘自http://blog.csdn.net/howeverpf/article/details/40743705 Wireshark入门与进阶系列(二) “君子生非异也,善假于物也”---荀子 本文由CSDN-蚍蜉撼青松 [主页:http://blog.csdn.net/howeverpf]原创,转载请注明出处! 上一篇文章我们讲了使用Wireshark进行数据包捕获与保存的最基本流程,更通常的情况下,我们对于要捕获的数据包及其展示.存储可能有一定要求,例如: 我们希望捕获的数据包中对我们有用

MVC过滤器:自定义授权过滤器

一.授权过滤器 授权过滤器用于实现IAuthorizationFilter接口和做出关于是否执行操作方法(如执行身份验证或验证请求的属性)的安全策略.AuthorizeAttribute类继承了IAuthorizationFilter接口,是授权过滤器的示例.授权过滤器在任何其他过滤器之前运行. 如果要自定义授权过滤器,只需要定义一个类继承自AuthorizeAttribute类,然后重写AuthorizeAttribute类里面的方法即可. 二.示例 下面根据一个具体的案例来讲解如何使用自定义

MVC过滤器:自定义操作过滤器

一.操作过滤器 1.定义 操作过滤器用于实现IActionFilter接口以及包装操作方法执行.IActionFilter接口声明两个方法:OnActionExecuting和OnActionExecuted.OnActionExecuting在操作方法之前运行.OnActionExecuted在操作方法之后运行,可以执行其他处理,如向操作方法提供额外数据.检查返回值或取消执行操作方法. 查看ActionFilterAttribute类的定义: #region 程序集 System.Web.Mv

ASP.NET MVC学习系列(二)-WebAPI请求

继续接着上文 ASP.NET MVC学习系列(一)-WebAPI初探 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用Jquery 来发起异步请求实现数据调用. 继续使用上一文章中的示例,添加一个index.html页面,添加对jquery的引用. 一.无参数Get请求 一般的get请求我们可以使用jquery提供的$.get() 或者$.ajax({type:"get"}) 来实现: 请求的后台Action方法仍为上篇文章中的GetU

highcharts 结合phantomjs纯后台生成图片系列二之php2

上篇文章中介绍了phantomjs的使用场景,方法. 本篇文章详细介绍使用php,highcharts 结合phantomjs纯后台生成图片.包含一步步详细的php代码 一.highcharts 结合phantomjs纯后台生成图片系列的准备: 下载phantomjs解析插件,从highcharts官方下载所需插件. 新建一个工程文件夹phantomjs,所必备的js文件有: highcharts 结合phantomjs纯后台生成图片系列二之php 其中jquery.js为 v1.7.1; hi

iOS开发UINavigation系列二——UINavigationItem

iOS开发UINavigation系列二--UINavigationItem 一.引言 UINavigationItem是导航栏上用于管理导航项的类,在上一篇博客中,我们知道导航栏是通过push与pop的堆栈操作来对item进行管理的,同样,每一个Item自身也有许多属性可供我们进行自定制.这篇博客,主要讨论UINavigationItem的使用方法. UINavigationBar:http://my.oschina.net/u/2340880/blog/527706. 二.来说说UINavi