詳解布隆過(guò)濾器

原創(chuàng)不易氮帐,轉(zhuǎn)載請(qǐng)注名出處:http://www.reibang.com/p/c98427267fb4

為什么用

去重是數(shù)據(jù)采集中很重要的一個(gè)點(diǎn)隘截,如果一個(gè)任務(wù)被反復(fù)放到任務(wù)列表中扎阶,這無(wú)疑是對(duì)資源最大的浪費(fèi)。同樣一個(gè)結(jié)果反復(fù)的入庫(kù)也是對(duì)數(shù)據(jù)庫(kù)資源的浪費(fèi)婶芭,且先不說(shuō)內(nèi)存占用會(huì)很大东臀,數(shù)據(jù)變很亂,你存表的時(shí)候唯一鍵也會(huì)讓在寫存表的腳本時(shí)異诚考慮占用到你過(guò)多的時(shí)間惰赋。
但是別慌我們有辦法,conner下面介紹一個(gè)很有用的工具呵哨,布隆過(guò)濾器赁濒,會(huì)讓你的代碼結(jié)構(gòu)變得更加的清晰,也可以應(yīng)對(duì)大批量的去重任務(wù)孟害。

原理

當(dāng)我判斷一張臉的時(shí)候流部,如果眼睛,鼻子纹坐,嘴巴,眉毛舞丛,耳朵都和我們腦海中的某個(gè)人的這些特征是一模一樣的話耘子,我們就有理由相信,這張臉我們認(rèn)識(shí)球切。
同樣這種方式我們放到算法中去谷誓,其實(shí)也是蠻好理解的。如果有個(gè)數(shù)據(jù)它通過(guò)不同方式來(lái)進(jìn)行hash(hash可以理解為找這一張臉的五官)吨凑,我們將每次hash后的值都存放到某個(gè)空間中捍歪。如果每次hash之后的值户辱,都能在我們的空間中能找到的話,我們就有足夠的理由相信糙臼,這個(gè)數(shù)據(jù)是在我們庫(kù)中存在的庐镐。
有人會(huì)問(wèn),既然這樣我們?yōu)槭裁床话颜麖埬槾娴綌?shù)據(jù)庫(kù)中变逃,然后來(lái)比較必逆?你需要先思考清楚,存一張臉和存五官哪個(gè)占用的內(nèi)存大哪個(gè)方便揽乱?當(dāng)然只通過(guò)幾個(gè)特征就想來(lái)識(shí)別一張臉名眉,是會(huì)存在一定程度的誤判。這種誤判可能會(huì)將不存在的某個(gè)數(shù)據(jù)錯(cuò)判斷為存在的數(shù)據(jù)凰棉,但是它一定不會(huì)將存在的值判斷為不存在损拢,而且這個(gè)誤判率其實(shí)是一個(gè)概率,但其實(shí)我們可以控制這個(gè)概率撒犀。
所以可以理解為布隆過(guò)濾器的數(shù)據(jù)結(jié)構(gòu)就是在你的程序里有一堆在記錄有和沒(méi)有的東西福压,如下圖:
假裝有個(gè)圖(好吧其實(shí)我懶得畫)

實(shí)現(xiàn)

關(guān)鍵其實(shí)需要抓住以下幾個(gè)點(diǎn):
1,hash(生成我們需要的特征绘证,包括hash的次數(shù)隧膏,以及我們需要hash的方法)。
2嚷那,exits判斷這個(gè)值的hash結(jié)果是否都在我們的空間中胞枕。
3,add將hash后到結(jié)果加入到我們的空間中魏宽。

hash算法確定

下面我分別使用python和java實(shí)現(xiàn)一次
hash方法我們選擇使用google的mmh3方法腐泻,這個(gè)方法的好處是能將一個(gè)值hash成多個(gè)值,而且這個(gè)值我們可以指定為64位還是32位队询,比較方便派桩。這個(gè)方法是用c++來(lái)實(shí)現(xiàn)的,python沒(méi)有辦法看到源碼蚌斩,java版能看到他多位hash的部分邏輯铆惑,有興趣的可以看看。

hash次數(shù)以及內(nèi)存大小的確定送膳,具體計(jì)算方式可以參考這篇博客:

https://my.oschina.net/LucasZhu/blog/1813110
感興趣的可以看看這里我們只關(guān)注結(jié)果:
假如我們需要去重的量capacity=100000000, 可以接受的錯(cuò)誤率error_rate=0.00000001
那么我們所需要的二進(jìn)制位是
m = math.ceil(capacity * math.log2(math.e) * math.log2(1 / error_rate))等于3834023351
需要的內(nèi)存數(shù)是:
mem = math.ceil(m / 8 / 1024 / 1024) 等于458mb
至少需要的hash次數(shù)是:
k = math.ceil(math.log1p(2) * m / capacity) 等于43次
主要有三個(gè)個(gè)方法员魏,hash,add和exits
所以下面開始展示我的代碼(部分)
python:

    def get_hashs(self, value):
        hashs = list()
        for seed in self.seeds:
            hash = mmh3.hash(value, seed)
            if hash >= 0:
                hashs.append(hash)
            else:
                hashs.append(self.N - hash)
        return hashs

    def is_exist(self, value):
        name = self.key + "_" + str(ord(value[0]) % self.blocknum)
        hashs = self.get_hashs(value)
        exist = True
        for hash in hashs:
            exist = exist & self.redis.getbit(name, hash)
        return exist

    def add(self, value):
        name = self.key + "_" + str(ord(value[0]) % self.blocknum)
        hashs = self.get_hashs(value)
        for hash in hashs:
            self.redis.setbit(name, hash, 1)

java:

public boolean add(byte[] bytes) {
        if (contains(bytes)) return true;
        long hash64 = Hashing.murmur3_128().hashObject(bytes, funnel).asLong();
        int hash1 = (int)hash64;
        int hash2 = (int)(hash64 >>> 32);
        boolean success = true;
        for(int i = 1; i <= numHashFunctions; ++i) {
            int nextHash = hash1 + i * hash2;
            long hashValue;
            if(nextHash < 0) {
                hashValue = Integer.MAX_VALUE + Math.abs((long) nextHash);
            } else {
                hashValue = nextHash;
            }
            success &= mmapData.setBit(hashValue);
        }
        if (success) mmapData.increment();
        return success;
    }

    public boolean contains(byte[] bytes) {
        hashMemNo = additiveHash(bytes,need_memory_time);
        long hash64 = Hashing.murmur3_128().hashObject(bytes, funnel).asLong();
        int hash1 = (int)hash64;
        int hash2 = (int)(hash64 >>> 32);
        for(int i = 1; i <= numHashFunctions; ++i) {
            int nextHash = hash1 + i * hash2;
            long hashValue;
            if(nextHash < 0) {
                hashValue = ((long) Integer.MAX_VALUE) + Math.abs(nextHash);
            } else {
                hashValue = nextHash;
            }
            if(mmapData.getBit(hashValue,hashMemNo) == 0) {
                return false;
            }
        }
        return true;
    }

優(yōu)勢(shì)和潛在問(wèn)題

優(yōu)勢(shì)

其實(shí)布隆過(guò)濾器本身的結(jié)構(gòu)就是最省內(nèi)存的叠聋,因?yàn)橹挥玫蕉M(jìn)制撕阎,它的每一個(gè)byte上只用0和1,0代表這個(gè)位置上沒(méi)有碌补,1代表代表這個(gè)位置有虏束,當(dāng)數(shù)據(jù)量少的時(shí)候這個(gè)問(wèn)題其實(shí)很好解決棉饶,但當(dāng)去重?cái)?shù)據(jù)量破億的時(shí)候,就會(huì)出現(xiàn)問(wèn)題镇匀。因?yàn)槟阈枰目臻g可能夠照藻,但是計(jì)數(shù)的方式可能會(huì)不夠,在下面我會(huì)再討論坑律。

尋址問(wèn)題

眾所周知岩梳,int類型的長(zhǎng)度2的32次方,因?yàn)榈谝晃槐硎镜氖钦?fù)號(hào)晃择,所以int能表示的最大值是2的31次方減去1也就是2147483647冀值,當(dāng)我們位置的值大于這個(gè)值時(shí)就得換到long類型,long類型的最大值是2的63次方減去1宫屠,看起來(lái)還是ok的列疗。但是當(dāng)我們將這個(gè)值轉(zhuǎn)換為過(guò)濾器中需要的內(nèi)存里空間的時(shí)就有點(diǎn)嚇人了,2的63次方個(gè)byte位需要的空間是2的35次方個(gè)gb的空間浪蹂,這個(gè)空間太大了怎么處理抵栈?以后有機(jī)會(huì)我會(huì)講到。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坤次,一起剝皮案震驚了整個(gè)濱河市古劲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缰猴,老刑警劉巖产艾,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異滑绒,居然都是意外死亡闷堡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門疑故,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)杠览,“玉大人,你說(shuō)我怎么就攤上這事纵势□獍ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵钦铁,是天一觀的道長(zhǎng)扫茅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)育瓜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任栽烂,我火速辦了婚禮躏仇,結(jié)果婚禮上恋脚,老公的妹妹穿的比我還像新娘。我一直安慰自己焰手,他們只是感情好糟描,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著书妻,像睡著了一般船响。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躲履,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天见间,我揣著相機(jī)與錄音,去河邊找鬼工猜。 笑死米诉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篷帅。 我是一名探鬼主播史侣,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼魏身!你這毒婦竟也來(lái)了惊橱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箭昵,失蹤者是張志新(化名)和其女友劉穎税朴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宙枷,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掉房,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慰丛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卓囚。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诅病,靈堂內(nèi)的尸體忽然破棺而出哪亿,到底是詐尸還是另有隱情,我是刑警寧澤贤笆,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布蝇棉,位于F島的核電站,受9級(jí)特大地震影響芥永,放射性物質(zhì)發(fā)生泄漏篡殷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一埋涧、第九天 我趴在偏房一處隱蔽的房頂上張望板辽。 院中可真熱鬧奇瘦,春花似錦、人聲如沸劲弦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)邑跪。三九已至次坡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間画畅,已是汗流浹背砸琅。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夜赵,地道東北人明棍。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寇僧,于是被迫代替她去往敵國(guó)和親摊腋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容