假設你現在要處理這樣乙個問題,你有乙個**並且擁有很多訪客,每當有使用者訪問時,你想知道這個ip是不是第一次訪問你的**。這是乙個很常見的場景,為了完成這個功能,你很容易就會想到下面這個解決方案:
把訪客的ip存進乙個hash表中,每當有新的訪客到來時,先檢查雜湊表中是否有改訪客的ip,如果有則說明該訪客在黑名單中。你還知道,hash表的訪問時間複雜度都是o(1),效率很高,因此你對你的方案很是滿意。
然後我們假設你的**已經被1億個使用者訪問過,每個ip的長度是15,那麼你一共需要15 * 100000000 = 1500000000bytes = 1.4g,這還沒考慮hash衝突的問題(hash表中的槽位越多,越浪費空間,槽位越少,效率越低)。
於是聰明的你稍一思考,又想到可以把ip轉換成無符號的int型值來儲存,這樣乙個ip只需要占用4個位元組就行了,這時1億個ip占用的空間是4 * 100000000 = 400000000bytes = 380m,空間消耗降低了很多。
那還有沒有在不影響訪問效率的前提下更加節省空間的辦法呢?
有32位無符號int型能表示的最大值是4294967295,所有的ip都在這個範圍內,我們可以用乙個bit位來表示某個ip是否出現過,如果出現過,就把代表該ip的bit位置為1,那麼我們最多需要429496729個bit就可以表示所有的ip了。舉個例子比如10.0.0.1轉換成int是167772161,那麼把長度為4294967295的bit陣列的第167772161個位置置為1即可,當有ip訪問時,只需要檢查該標誌位是否為1就行了。
4294967295bit = 536870912byte = 512m。如果用hash表示所有4294967295範圍內的陣列的話,需要十幾g的空間。
當然,這裡舉ip的例子不一定合適,主要目的是為了引出bitset。
下面我們來看看bitset具體怎樣實現。
首先,比如我們有乙個長度=2的byte陣列,2個位元組一共有16位,可以表示0-15的數字是否存在。比如我們要驗證11是否出現過,那麼我們先檢查第11個位置是否為1,如果為0,說明11沒出現過,然後我們把第11位置為1,表示11已經出現過了
所以,bitset基本只有兩個操作,set(int value) 和 ishas(int value)
我們先來看set怎麼實現,因為乙個byte佔8位,所以對於乙個給定的value,我們先求出該value應該位於哪個byte上,這很簡單,int byteindex = value / 8;
找到value在byte陣列中的位置後,再就是在該位元組中尋找表示value的bit位,我們知道,乙個byte其實就是乙個長為8的bit陣列,那麼value在該bit陣列中的位置也就很好算了,int bitindex = value % 8;
最後我們把該bit位設定為1就可以了:byte[byteindex] = byte[byteindex] | 1 << ( 7 - bitindex)
整個流程如下面所示:
public
void
set(
int value)
同樣的,ishas(int value)的推導過程和set(int value)差不多:
public
boolean
ishash
(int value)
想必有同學已經意識到了bitset使用起來似乎並不總是那麼完美,bitset有兩個比較侷限的地方:
當樣本分佈極度不均勻的時候,bitset會造成很大空間上的浪費。
舉個例子,比如你有10個數,分別是1、2、3、4、5、6、7、8、99999999999;那麼你不得不用99999999999個bit位去實現你的bitset,而這個bitset的中間絕大多數字置都是0,並且永遠不會用到,這顯然是極度不划算的。
當元素不是整型的時候,bitset就不適用了。
想想看,你拿到的是一堆url,然後如果你想用bitset做去重的話,先得把url轉換成int型,在轉換的過程中難免某些url會計算出相同的int值,於是bitset的準確性就會降低。
那針對這兩種情況有沒有解決辦法呢?
第一種分布不均勻的情況可以通過hash函式,將元素都對映到乙個區間範圍內,減少大段區間閒置造成的浪費,這很簡單,取模就好了,難的是取模之後的值保證不相同,即不發生hash衝突。
第二種情況,把字串對映成整數是必要的,那麼唯一要做的就是保證我們的hash函式盡可能的減少hash衝突,一次不行我就多hash幾次,hash還是容易碰撞,那我就擴大陣列的範圍,使hash值盡可能的均勻分布,減少hash衝突的概率。
基於這種思想,bloomfilter誕生了。
bloomfiler又叫布隆過濾器,下面舉例說明bloofilter的實現原理:
比如你有10個url,你完全可以建立一長度是100bit的陣列,然後對url分別用5個不同的hash函式進行hash,得到5個hash後的值,這5個值盡可能的保證均勻分布在100個bit的範圍內。然後把5個hash值對應的bit位都置為1,判斷乙個url是否已經存在時,一次看5個bit位是否為1就可以了,如果有任何乙個不為1,那麼說明這個url不存在。這裡需要注意的是,如果對應的bit位值都為1,那麼也不能肯定這個url一定存在,這個是bloomfilter的特點之一,稍後再說。
bloomfilter的核心思想有兩點:
多個hash,增大隨機性,減少hash碰撞的概率
擴大陣列範圍,使hash值均勻分布,進一步減少hash碰撞的概率。
儘管bloomfilter已經盡可能的減小hash碰撞的概率了,但是,並不能徹底消除,因此正如上面提到的:
如果對應的bit位值都為1,那麼也不能肯定這個url一定存在
也就是說,bloomfilter其實是存在一定的誤判的,這個誤判的概率顯然和陣列的大小以及hash函式的個數以及每個hash函式本身的好壞有關,具體的計算公式,可以查閱相關**,這裡只給出結果:
wiki的bloom filter詞條有關於誤報的概率的詳細分析:probability of false positives。從分析可以看出,誤判概率還是比較小的,空間利用率也很高。
黑名單比如郵件黑名單過濾器,判斷郵件位址是否在黑名單中
排序(僅限於bitset)
仔細想想,其實bitset在set(int value)的時候,「順便」把value也給排序了。
網路爬蟲
判斷某個url是否已經被爬取過
k-v系統快速判斷某個key是否存在
布隆過濾器
布隆過濾器 bloom filter 是1970年由布隆提出的。它實際上是乙個很長的二進位制向量和一系列隨機對映函式。布隆過濾器可以用於檢索乙個元素是否在乙個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。如果想要判斷乙個元素是不是在乙個集合裡,一般想到...
布隆過濾器
布隆過濾器的概念 如果想要判斷乙個元素是不是在乙個集合裡,一般想到的是將所有元素儲存起來,然後通過比較確定。鍊錶,樹等等資料結構都是這種思路.但是隨著集合中元素的增加,我們需要的儲存空間越來越大,檢索速度也越來越慢 o n o logn 不過世界上還有一種叫作雜湊表 又叫 雜湊表,hash tabl...
布隆過濾器
如果想判斷乙個元素是不是在乙個集合裡,一般想到的是將集合中所有元素儲存起來,然後通過比較確定。鍊錶 樹 雜湊表 又叫雜湊表,hash table 等等資料結構都是這種思路。但是隨著集合中元素的增加,我們需要的儲存空間越來越大。同時檢索速度也越來越慢。bloom filter 是一種空間效率很高的隨機...