布隆過濾器是由Burton Bloom與1970年提出來的,因此它的名字就叫作Bloom Filter。它其實是一個很長的二進制向量和一系列的隨機映射函數。php
一個空的布隆過濾器是由m個bits組成的bit array,每個bit位都初始爲0。而且定義有k個不一樣的哈希函數,每一個哈希函數都將元素哈希到bit array的不一樣位置。git
當添加一個元素時,用k個哈希函數分別將它hash獲得k個bit位,而後將這些bit位置位1。算法
查詢一個函數時,一樣用k個哈希函數將它hash,再判斷k個bit位上是否都爲1,若是其中某一位爲0,則該元素不存在於布隆過濾器中。數據庫
常規的布隆過濾器不容許執行刪除元素操做,由於那樣會把k個bits位置位0,而其中某一位可能和其餘元素想對應。所以刪除操做會引入false negative,若是須要刪除操做可使用Counting Bloom Filter
編程
當k很大時,設計k個獨立的哈希函數是不現實的。對於一個輸出範圍很大的哈希函數(MD5產生的128 bits),若是不一樣bits的相關性很小,則能夠把此輸出分割位k份。或者將k個不一樣的初始值結合元素,feed給一個哈希函數從而產生k個不一樣的值。網頁爬蟲
就以垃圾郵件過濾爲例,假定咱們有一億個垃圾郵件地址,每一個郵件用8個hash函數來生成8個信息指紋,由於在保證誤判率低且k和m選取合適時,空間利用率爲50%。因此咱們的m(布隆過濾器的槽數)爲緩存
,也就是16億個二進制位。咱們先將全部二進制位所有清零。對於每一個郵件地址X,咱們用8個不一樣的hash函數進行hash,再將這8個信息指紋映射到1-16億中的8個天然數g1,g2,...g8。如今將這8個位置的二進制值所有置爲1。對一億個郵件地址都進行這樣的處理後,咱們的布隆過濾器也就建成功了。當咱們要判斷一個郵件地址是否在布隆過濾器中時,須要使用相同的8個hash函數來將8個信息指紋對應到布隆過濾器的8個二進制位上。若是8個二進制位的值只要有一個或更多爲0,那麼它必定不存在於布隆中。若是8個值全都爲1,那麼它可能存在於布隆中,這是由於誤識別致使的。服務器
相對於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優點。布隆過濾器存儲空間和插入/查詢時間都是常數。另外,hash函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不須要存儲數據自己,在某些對保密要求很是嚴格的場合由優點。數據結構
布隆過濾器的缺點和其優勢同樣明顯。誤算率(False Positive)是其中之一。隨着存入元素的數量增長,誤算率隨之增長。ide
在上面的案例中,咱們說到過關於布隆的誤算率的問題,這在檢驗上被稱爲假陽性
。
估算假陽性的機率並不難。假定布隆過濾器有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比較大,能夠近似爲:
class BloomFilterHash {
/** * 由Justin Sobel 編寫的按位散列函數. * * @param string $string * @param null $len * @return int */
public function JSHash($string, $len = null) {
$hash = 1315423911;
$len || $len = strlen($string);
for ($i = 0; $i < $len; $i ++) {
$hash ^= (($hash << 5) + ord($string[$i]) + ($hash >> 2));
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 該哈希算法基於AT&T貝爾實驗室的Peter J. Weinberger的工做。 * Aho Sethi和Ulman編寫的「編譯器(原理,技術和工具)」一書建議使用採用此特定算法中的散列方法的散列函數。 * * @param string $string * @param null $len * @return int */
public function PJWHash($string, $len = null) {
$bitsInUnsignedInt = 4 * 8;
$threeQuarters = ($bitsInUnsignedInt * 3) / 4;
$oneEighth = $bitsInUnsignedInt / 8;
$highBits = 0xFFFFFFFF << (int) ($bitsInUnsignedInt - $oneEighth);
$hash = 0;
$test = 0;
$len || $len = strlen($string);
for ($i = 0; $i < $len; $i ++) {
$hash = ($hash << (int) ($oneEighth)) + ord($string[$i]);
}
$test = $hash & $highBits;
if ($test != 0) {
$hash = (($hash ^ ($test >> (int)($threeQuarters))) & (~$highBits));
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 相似PJW Hash功能,可是針對32位處理器作了調整。它是基於unix系統上的widely使用哈希函數。 * * @param string $string * @param null $len * @return int */
public function ELEHash($string, $len = null) {
$hash = 0;
$len || $len = strlen($string);
for ($i = 0; $i < $len; $i++) {
$hash = ($hash << 4) + ord($string[$i]);
$x = $hash & 0xF0000000;
if ($x != 0) {
$hash ^= ($x >> 24);
}
$hash &= ~$x;
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 這個哈希函數來自Brian Kernighan和Dennis Ritchie的書「The C Programming Language」。 * 它是一個簡單的哈希函數,使用一組奇怪的可能種子,它們都構成了31 .... 31 ... 31等模式,它彷佛與DJB哈希函數很是類似。 */
public function BKDRHash($string, $len = null) {
$seed = 131; # 31 131 1313 13131 131313 etc..
$hash = 0;
$len || $len = strlen($string);
for ($i=0; $i<$len; $i++) {
$hash = (int) (($hash * $seed) + ord($string[$i]));
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 這是在開源SDBM項目中使用的首選算法。 * 哈希函數彷佛對許多不一樣的數據集具備良好的整體分佈。它彷佛適用於數據集中元素的MSB存在高差別的狀況。 */
public function SDBMHash($string, $len = null) {
$hash = 0;
$len || $len = strlen($string);
for ($i=0; $i<$len; $i++) {
$hash = (int) (ord($string[$i]) + ($hash << 6) + ($hash << 16) - $hash);
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 由Daniel J. Bernstein教授製做的算法,首先在usenet新聞組comp.lang.c上向世界展現。 * 它是有史以來發布的最有效的哈希函數之一。 */
public function DJBHash($string, $len = null) {
$hash = 5381;
$len || $len = strlen($string);
for ($i=0; $i<$len; $i++) {
$hash = (int) (($hash << 5) + $hash) + ord($string[$i]);
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * Donald E. Knuth在「計算機編程藝術第3卷」中提出的算法,主題是排序和搜索第6.4章。 */
public function DEKHash($string, $len = null) {
$len || $len = strlen($string);
$hash = $len;
for ($i=0; $i<$len; $i++) {
$hash = (($hash << 5) ^ ($hash >> 27)) ^ ord($string[$i]);
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
/** * 參考 http://www.isthe.com/chongo/tech/comp/fnv/ */
public function FNVHash($string, $len = null) {
$prime = 16777619; //32位的prime 2^24 + 2^8 + 0x93 = 16777619
$hash = 2166136261; //32位的offset
$len || $len = strlen($string);
for ($i=0; $i<$len; $i++) {
$hash = (int) ($hash * $prime) % 0xFFFFFFFF;
$hash ^= ord($string[$i]);
}
return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
}
}
複製代碼
abstract class BloomFilterRedis {
/** * 須要使用一個方法來定義bucket名字. */
protected $bucket;
protected $hashFunction;
public function __construct() {
if (!$this->bucket || !$this->hashFunction) {
throw new Exception("須要定義bucket和hashFunction");
}
$this->Hash = new BloomFilterHash;
$this->Redis = new \Redis(); // 假設已經鏈接好了
$this->Redis->connect('127.0.0.1');
}
/** * @param $string * @return array */
public function add($string) {
$pipe = $this->Redis->multi();
foreach ($this->hashFunction as $function) {
$hash = $this->Hash->$function($string);
$pipe->setBit($this->bucket, $hash, 1);
}
return $pipe->exec();
}
/** * 查詢是否存在,不存在的必定不存在,存在的可能存在誤判. * * @param $string * @return bool */
public function exists($string) {
$pipe = $this->Redis->multi();
$len = strlen($string);
foreach ($this->hashFunction as $function) {
$hash = $this->Hash->$function($string, $len);
$pipe = $pipe->getBit($this->bucket, $hash);
}
$res = $pipe->exec();
foreach ($res as $bit) {
if ($bit == 0) {
return false;
}
}
return true;
}
}
複製代碼
class FilteRepeatedComments extends BloomFilterRedis {
protected $bucket = 'rptc';
protected $hashFunction = array('BKDRHash', 'SDBMHash', 'JSHash');
}
複製代碼