hashing 与 bloom filter

来源:互联网 发布:淘宝水印在哪里设置 编辑:程序博客网 时间:2024/06/05 03:45

关于hashing

The most important techniques behind Yahoo! are: hashing, hashing and hashing!
——前雅虎首席科学家Udi Manber
 
计算机科学中的一类基本问题是如何在内存中找到一段指定的信息(a “key”),这类问题常常被称为字典问题(dictionary problem)。对这类问题可以提出很多解决方案,但考虑的关键因素之一就是:如何在数据量快速增长的同时仍然保证查找的速度。Hashing就是满足这个条件的一种高效解决方案。
 
在解释hashing之前先来看一看key这个概念。根据NIST的定义,key是一组数据中的一部分,通过这部分信息来存储、索引整组信息。比如如果要给一组客户记录排序,就可以将客户姓名选作key,从而根据姓名按照字典序排列。这里要注意的一点是,key的选择不是绝对的,在不同的应用场合下,对同一组信息可能选择不同的key。例如,如果要将上面提到的客户记录作成财务汇报,那么key就可以选作客户的交易金额,从而根据交易金额进行排序。
 
Key的取值范围通常很大并且分布不均,哈希函数的目的就是将key映射到分布相对均匀且较小的整数集合。从很大的集合到较小的集合,从分布不均到分布均匀,这是哈希函数的两个基本特点。对于哈希函数的使用者来说,哈希函数既有随机性,又有确定性。随机性是指给定一个key,哈希函数的使用者完全不能预测这个key到底会被映射到哪个整数;确定性是指给定一个key,同一个哈希函数总会将它映射到同一个整数。
 
哈希函数的随机性保证了其对输入key的加密特性。通常情况下,哈希函数的输出值能够唯一标识输入的key,因此就像现实世界中的“指纹”能够唯一标识一个人一样,哈希函数的输出值也被叫做“数字指纹”(digital fingerprint)。当然,这只是哈希函数期望达到的境界,理论上由于哈希函数将大集合映射到了小集合,碰撞的可能一定存在。最近,山东大学的王小云教授(已经被挖到了清华)就破解了国际上流行的MD5和SHA-1两大哈希算法,在密码界引起了轩然大波。实际上,破解的过程就是进行碰撞攻击(collision attack),从而找到两个key映射到同一输出值的情况,这样就可以伪造数字指纹。
 
哈希函数的输出值能够唯一标识一个key,这本身就反映了哈希函数的确定性。在哈希表中,哈希函数被用来生成key的存储地址,正是由于确定性的存在,使得存储后的查找成为可能。哈希表最大的特点,就是它不随数据量的增大而速度变慢,因为记忆数据存储位置的任务交给了哈希函数。每一次查找数据的时间都是恒定的,即哈希函数的计算时间(不考虑碰撞的情况下)。这里我们可以看到哈希函数另一大作用:作为存储信息的载体。
 
如果我们想记录某个集合的哈希表地址,一般情况下我们会考虑将这个集合的哈希表地址存储在内存中,这无疑要消耗大量的空间,而且常常不可实现。在计算机科学中,时间换空间的情况经常发生,这里再一次印证了这个观点。为了不占用内存,我们设计合适的哈希函数来存储地址信息,在需要新的地址时,通过占用一定的CPU时间算出新地址。如同不同的情况下存储模式不同一样,在不同的应用场合中,也需要设计满足特定要求的哈希函数。例如,密码学中的哈希算法更多地考虑如何躲避恶意的攻击和伪造,而用在检错和纠错领域的哈希算法则更多地考虑如何将改动过的数据区分开来。具体的哈希算法会在后面介绍,这里就不多讲了。
 
 
Dictionary, Direct-address Tables, Hash Tables
 

Dictionary是一种抽象数据类型,用来存储可以用键值(key)索引的数据项,基本的操作包括插入、查找和删除。它是一个相对比较广义的概念,并没有规定具体的实现,比如在底层用什么数据结构存储数据项。因此,只要存储的每一个数据项是一对(key, value),并可以用key索引到这一项,就可以将这样的数据类型称为Dictionary

 

Direct-address TablesHash Tables都是Dictionary的具体实现方式。Direct-address Tables其实就是普通的数组,数组的第k项只被用来存储键值为k的数据项。显然,要应用这种数据结构必须给每一个可能的键值预留一个数组项,因此它只适用于键值的集合比较小的情况。虽然Direct-address Tables看起来比较浪费内存,但也有它的优点:插入、查找和删除操作的时间复杂度为O(1)

 

Hash TablesDictionary的一种有效实现,它解决了Direct-address Tables在键值集合比较大的情况下不适用的问题。假设U表示所有可能的键值的集合,K表示实际要存储的键值的集合。在|U|远远大于|K|的时候,Hash Tables只分配和|K|大小成比例的数据表项,然后通过哈希函数将K映射到各个表项中。Hash Tables通过将取值范围很大的键值映射到较小的集合中,极大地节省了存储空间,但同时引入了碰撞(Collision)的因素,即几个键值映射到同一表项的情况。由于处理碰撞而增加的复杂度,常常使查找或删除等操作的时间复杂度不再为O(1),在最差的情况下甚至为O(n)n = |K|)。但在实际中,通过选择合适的哈希函数,上述操作的时间复杂度常常能控制在接近O(1)

 

fect Hashing VS. Bloom Filter

 

Perfect Hashing VS. Bloom Filter

Network Applications of Bloom Filters: A Survey一文中,作者提到了一种基于Perfect hashing的方法,它在维持同样错误率的情况下比Bloom Filter占用更少的空间。但是这种方法只能使用在静态集合上,一旦集合发生变化,就需要进行重新计算。

 

假设我们要表示的静态集合Xn个元素,我们针对它可以找到一个perfect hash function,记作hx : [1…u] [1…n]。所谓perfect hash function,即它针对不同的key能产生不同的hash value,也就是说没有collision。如果针对不同的key产生不同的hash value,且hash value分布在连续的整数区间内,则称之为minimal perfect hash function,或者minimal perfect hashing。所以上面提到的函数hx严格来说是一个minimal perfect hash function

 

有了hx,我们就可以将X映射到n个连续的格子(bucket)中,每个元素对应其中一个格子。下面我们还需要另一个hash function,它针对每个元素完全随机地生成j位长的hash value,然后将hash value作为这个元素的fingerprint存储在对应的格子里。记这个函数为φ: [1…u] → [0…2j-1]。

 

有了hx和φ,我们就可以分两步将X映射到一个m = n.j位的内存中,且查找的错误率为1/2j,因为只有在jfingerprint完全吻合的情况下才会出现false positive。在Bloom Filter概念和原理一文中,我们提到过Bloom Filter的错误率为(1/2)k(1/2)mln2/n。因此当m = n.j时,Bloom Filter的错误率为(0.6185)j,高于这种基于perfect hashing的方法。如果Bloom Filter要保持1/2j的错误率,必须有m = n .j / ln2,因此所占空间是基于perfect hashing方法的1 / ln2倍。

 

这种方法看起来很诱人,可惜只能用在静态集合上。在一些本身就具有静态集合特征的应用场合下,比如某种程序设计语言的关键字,或者某张光盘里的文件目录,它可以作为一种节省空间的方法得以应用。后续的文章中还会介绍一种d-left counting bloom filter,它借鉴了这种基于perfect hashing的方法,在保留counting bloom filter功能的前提下比它节省了大约一半空间。

 

   CSDN博客第三期云计算最佳博主评选

d-Left Hashing

分类: Hashing 2007-03-08 10:38 5269人阅读 评论(2)收藏 举报
存储扩展
 

下面介绍简单介绍一下d-left hashing。d-left hashing中的d是多个的意思,我们先简化这个问题,看一看2-left hashing。2-left hashing指的是将一个哈希表分成长度相等的两半,分别叫做T1和T2,给T1和T2分别配备一个哈希函数,h1和h2。在存储一个新的key时,同时用两个哈希函数进行计算,得出两个地址h1[key]和h2[key]。这时需要检查T1中的h1[key]位置和T2中的h2[key]位置,哪一个位置已经存储的(有碰撞的)key比较多,然后将新key存储在负载少的位置。如果两边一样多,比如两个位置都为空或者都存储了一个key,就把新key存储在左边的T1子表中,2-left也由此而来。在查找一个key时,必须进行两次hash,同时查找两个位置。

 

上面的介绍中有一点要注意,就是在作位置选择时,考虑的是两个哈希函数映射的位置中已经存储的key(包括碰撞的情况)的个数,而不是两个子表中已有key的个数。

 

了解了2-left hashing,d-left hashing就很好理解,它只是对前者的扩展。2-left hashing固定了子表的个数是2,d-left hashing更加灵活,子表的个数是一个变量d,同时也意味着哈希函数的个数是d。在d-left hashing中,整个哈希表被分成d个从左到右依次相邻的子表,每个子表对应一个相互独立的哈希函数。在加入新key时,这个key被d个哈希函数同时计算,产生d个相互独立的位置,然后将key加入到负载最轻的位置(bucket)中。如果负载最轻的位置有多个,就把key加入到最左边的负载最轻的子表中。同样地,如果要查找一个key,需要同时查找d个位置。

 

 

原创粉丝点击