那些有趣的算法之布隆过滤器

Posted 掘金开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些有趣的算法之布隆过滤器相关的知识,希望对你有一定的参考价值。

布隆过滤器是由 Burton Bloom 与 1970 年提出来的,所以它的名字就叫做 Bloom Filter。它实际上是一个很长的二进制向量和一系列的随机映射函数。

使用场景

  1. 有的黑客为了让服务宕机,他们会构建大量不存在于缓存中的 key 向服务器发起请求,在数据量足够大的情况下,频繁的数据库查询可能导致 DB 挂掉。布隆过滤器很好的解决了缓存击穿的问题。

  2. 反垃圾邮件,从数十亿个垃圾邮件列表中判断某个邮箱是否是垃圾邮箱。

  3. ...

算法描述

一个空的布隆过滤器是由 m 个bits组成的 bit array,每一个 bit 位都初始为 0。并且定义有 k 个不同的哈希函数,每个哈希函数都将元素哈希到 bit array 的不同位置。

当添加一个元素时,用k个哈希函数分别将它 hash 得到 k 个 bit 位,然后将这些 bit 位置位 1。

查询一个函数时,同样用 k 个哈希函数将它 hash,再判断 k 个 bit 位上是否都为 1,如果其中某一位为 0,则该元素不存在于布隆过滤器中。

常规的布隆过滤器不允许执行删除元素操作,因为那样会把 k 个 bits 位置位 0,而其中某一位可能和其他元素想对应。因此删除操作会引入 false negative,如果需要删除操作可以使用 CountingBloomFilter

当k很大时,设计k个独立的哈希函数是不现实的。对于一个输出范围很大的哈希函数(MD5 产生的 128 bits),如果不同 bits 的相关性很小,则可以把此输出分割位 k 份。或者将 k 个不同的初始值结合元素,feed 给一个哈希函数从而产生 k 个不同的值。

举例说明

那些有趣的算法之布隆过滤器

那些有趣的算法之布隆过滤器

优势

相对于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外,hash 函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储数据本身,在某些对保密要求非常严格的场合由优势。

缺点

布隆过滤器的缺点和其优点一样明显。误算率(False Positive)是其中之一。随着存入元素的数量增加,误算率随之增加。

误判概率的证明和计算

在上面的案例中,我们说到过关于布隆的误算率的问题,这在检验上被称为 假阳性

估算假阳性的概率并不难。假定布隆过滤器有 m比特,里面有 n个元素,每个元素对应 k个信息指纹的哈希函数,当然这里 m比特里有些是 0 有些是 1。我们先来看看某个比特为 0 的概率。当我们在插入一个元素时,它的第一个哈希函数会把过滤器中的某个比特置为 1,因此,任何一个比特被置为 1 的概率是  1/m,它依然为 0 的概率则为 1-1/m。对于过滤器中的某个特定位置,如果这个元素 k 个哈希函数都没有把它设置为1,其概率是 (1-1/m)^k。如果过滤器插入第二个元素,某个特定位置依然没有被设置为 1,其概率为 (1-1/m)^2k。如果插入了 n 个元素,还是没有把某个位置设置为1,其概率为 (1-1/m)^kn。反过来,一个比特在插入了 n 个元素后,被置为 1 的概率为 1-(1-1/m)^kn

现在假定这n个元素都放到了过滤器中,新来一个不在集合中的元素,由于它的信息指纹的哈希函数都是随机的,因此,它的第一个哈希函数正好命中某个值为 1 的比特的概率就是上述概率。一个不在集合中的元素被误识别为在集合中,所需要的哈希函数对应比特的值均为 1,其概率为:

那些有趣的算法之布隆过滤器

化简后为:

如果 n 比较大,可以近似为:

php 实现

 
   
   
 
  1. class BloomFilterHash

  2. {

  3. /**

  4. * 由Justin Sobel 编写的按位散列函数.

  5. *

  6. * @param string $string

  7. * @param null $len

  8. * @return int

  9. */

  10. public function JSHash($string, $len = null)

  11. {

  12. $hash = 1315423911;

  13. $len || $len = strlen($string);

  14. for ($i = 0; $i < $len; $i ++) {

  15. $hash ^= (($hash << 5) + ord($string[$i]) + ($hash >> 2));

  16. }


  17. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  18. }


  19. /**

  20. * 该哈希算法基于AT&T贝尔实验室的Peter J. Weinberger的工作。

  21. * Aho Sethi和Ulman编写的“编译器(原理,技术和工具)”一书建议使用采用此特定算法中的散列方法的散列函数。

  22. *

  23. * @param string $string

  24. * @param null $len

  25. * @return int

  26. */

  27. public function PJWHash($string, $len = null)

  28. {

  29. $bitsInUnsignedInt = 4 * 8;

  30. $threeQuarters = ($bitsInUnsignedInt * 3) / 4;

  31. $oneEighth = $bitsInUnsignedInt / 8;

  32. $highBits = 0xFFFFFFFF << (int) ($bitsInUnsignedInt - $oneEighth);

  33. $hash = 0;

  34. $test = 0;

  35. $len || $len = strlen($string);

  36. for ($i = 0; $i < $len; $i ++) {

  37. $hash = ($hash << (int) ($oneEighth)) + ord($string[$i]);

  38. }

  39. $test = $hash & $highBits;

  40. if ($test != 0) {

  41. $hash = (($hash ^ ($test >> (int)($threeQuarters))) & (~$highBits));

  42. }

  43. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  44. }


  45. /**

  46. * 类似PJW Hash功能,但是针对32位处理器做了调整。它是基于unix系统上的widely使用哈希函数。

  47. *

  48. * @param string $string

  49. * @param null $len

  50. * @return int

  51. */

  52. public function ELEHash($string, $len = null)

  53. {

  54. $hash = 0;

  55. $len || $len = strlen($string);

  56. for ($i = 0; $i < $len; $i++) {

  57. $hash = ($hash << 4) + ord($string[$i]);

  58. $x = $hash & 0xF0000000;

  59. if ($x != 0) {

  60. $hash ^= ($x >> 24);

  61. }

  62. $hash &= ~$x;

  63. }


  64. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  65. }


  66. /**

  67. * 这个哈希函数来自Brian Kernighan和Dennis Ritchie的书“The C Programming Language”。

  68. * 它是一个简单的哈希函数,使用一组奇怪的可能种子,它们都构成了31 .... 31 ... 31等模式,它似乎与DJB哈希函数非常相似。

  69. */

  70. public function BKDRHash($string, $len = null)

  71. {

  72. $seed = 131; # 31 131 1313 13131 131313 etc..

  73. $hash = 0;

  74. $len || $len = strlen($string);

  75. for ($i=0; $i<$len; $i++) {

  76. $hash = (int) (($hash * $seed) + ord($string[$i]));

  77. }

  78. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  79. }


  80. /**

  81. * 这是在开源SDBM项目中使用的首选算法。

  82. * 哈希函数似乎对许多不同的数据集具有良好的总体分布。它似乎适用于数据集中元素的MSB存在高差异的情况。

  83. */

  84. public function SDBMHash($string, $len = null)

  85. {

  86. $hash = 0;

  87. $len || $len = strlen($string);

  88. for ($i=0; $i<$len; $i++) {

  89. $hash = (int) (ord($string[$i]) + ($hash << 6) + ($hash << 16) - $hash);

  90. }

  91. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  92. }


  93. /**

  94. * 由Daniel J. Bernstein教授制作的算法,首先在usenet新闻组comp.lang.c上向世界展示。

  95. * 它是有史以来发布的最有效的哈希函数之一。

  96. */

  97. public function DJBHash($string, $len = null)

  98. {

  99. $hash = 5381;

  100. $len || $len = strlen($string);

  101. for ($i=0; $i<$len; $i++) {

  102. $hash = (int) (($hash << 5) + $hash) + ord($string[$i]);

  103. }

  104. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  105. }


  106. /**

  107. * Donald E. Knuth在“计算机编程艺术第3卷”中提出的算法,主题是排序和搜索第6.4章。

  108. */

  109. public function DEKHash($string, $len = null)

  110. {

  111. $len || $len = strlen($string);

  112. $hash = $len;

  113. for ($i=0; $i<$len; $i++) {

  114. $hash = (($hash << 5) ^ ($hash >> 27)) ^ ord($string[$i]);

  115. }

  116. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  117. }


  118. /**

  119. * 参考 http://www.isthe.com/chongo/tech/comp/fnv/

  120. */

  121. public function FNVHash($string, $len = null)

  122. {

  123. $prime = 16777619; //32位的prime 2^24 + 2^8 + 0x93 = 16777619

  124. $hash = 2166136261; //32位的offset

  125. $len || $len = strlen($string);

  126. for ($i=0; $i<$len; $i++) {

  127. $hash = (int) ($hash * $prime) % 0xFFFFFFFF;

  128. $hash ^= ord($string[$i]);

  129. }

  130. return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;

  131. }

  132. }

 
   
   
 
  1. abstract class BloomFilterRedis

  2. {

  3. /**

  4. * 需要使用一个方法来定义bucket名字.

  5. */

  6. protected $bucket;


  7. protected $hashFunction;


  8. public function __construct()

  9. {

  10. if (!$this->bucket || !$this->hashFunction) {

  11. throw new Exception("需要定义bucket和hashFunction");

  12. }


  13. $this->Hash = new BloomFilterHash;

  14. $this->Redis = new \Redis(); // 假设已经连接好了

  15. $this->Redis->connect('127.0.0.1');

  16. }


  17. /**

  18. * @param $string

  19. * @return array

  20. */

  21. public function add($string)

  22. {

  23. $pipe = $this->Redis->multi();

  24. foreach ($this->hashFunction as $function) {

  25. $hash = $this->Hash->$function($string);

  26. $pipe->setBit($this->bucket, $hash, 1);

  27. }

  28. return $pipe->exec();

  29. }


  30. /**

  31. * 查询是否存在,不存在的一定不存在,存在的可能存在误判.

  32. *

  33. * @param $string

  34. * @return bool

  35. */

  36. public function exists($string)

  37. {

  38. $pipe = $this->Redis->multi();

  39. $len = strlen($string);

  40. foreach ($this->hashFunction as $function) {

  41. $hash = $this->Hash->$function($string, $len);

  42. $pipe = $pipe->getBit($this->bucket, $hash);

  43. }

  44. $res = $pipe->exec();

  45. foreach ($res as $bit) {

  46. if ($bit == 0) {

  47. return false;

  48. }

  49. }


  50. return true;

  51. }

  52. }

 
   
   
 
  1. class FilteRepeatedComments extends BloomFilterRedis

  2. {

  3. protected $bucket = 'rptc';


  4. protected $hashFunction = array('BKDRHash', 'SDBMHash', 'JSHash');

  5. }

小结


以上是关于那些有趣的算法之布隆过滤器的主要内容,如果未能解决你的问题,请参考以下文章

算法专题之bitmap与布隆过滤器 ----如何快速处理海量数据

亿级数据之过滤器布隆过滤器

干货|海量数据处理利器之布隆过滤器

数据结构之布隆过滤器

恋上数据结构与算法 —— 布隆过滤器

用Python实现一个大数据搜索及源代码