




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
第C#實現(xiàn)從位圖到布隆過濾器的方法目錄前言布隆過濾器簡介數(shù)據(jù)的存儲Hash沖突的解決方案為什么布隆過濾器不支持刪除用C#實現(xiàn)Bitmap位運算利用位運算創(chuàng)建Bitmap用C#實現(xiàn)布隆過濾器MurmurHash3的使用將任意類型的key轉(zhuǎn)換為byte數(shù)組Funnel與Sink的定義Sink的實現(xiàn)k個Hash函數(shù)與布隆過濾器實現(xiàn)擴展帶計數(shù)器的布隆過濾器分布式布隆過濾器實現(xiàn)方案代碼地址
前言
本文將以C#語言來實現(xiàn)一個簡單的布隆過濾器,為簡化說明,設(shè)計得很簡單,僅供學(xué)習(xí)使用。
感謝@時總百忙之中的指導(dǎo)。
布隆過濾器簡介
布隆過濾器(Bloomfilter)是一種特殊的HashTable,能夠以較小的存儲空間較快地判斷出數(shù)據(jù)是否存在。常用于允許一定誤判率的數(shù)據(jù)過濾及防止緩存擊穿及等場景。
相較于.NET中的HashSet這樣傳統(tǒng)的HashTable,存在以下的優(yōu)劣勢。
優(yōu)勢:
占用的存儲空間較小。不需要像HashSet一樣存儲Key的原始數(shù)據(jù)。
劣勢:
存在誤判率,過濾器認(rèn)為不存在的數(shù)據(jù)一定不存在,但是認(rèn)為存在的數(shù)據(jù)不一定真的存在。這個和布隆過濾器的實現(xiàn)方式有關(guān)。不支持?jǐn)?shù)據(jù)的刪除,下文會講為什么不支持刪除。
數(shù)據(jù)的存儲
布隆過濾器的數(shù)據(jù)保存在位圖(Bitmap)上。Bitmap簡而言之是二進制位(bit)的數(shù)組。HashTable保存每個元素的位置,我們稱之為桶(bucket),Bitmap上的每一位就是布隆過濾器的bucket。
布隆過濾器的每一個bucket只能存儲0或1。數(shù)據(jù)插入時,布隆過濾器會通過Hash函數(shù)計算出插入的key對應(yīng)的bucket,并將該bucket設(shè)置為1。
查詢時,再次根據(jù)Hash函數(shù)計算出key對應(yīng)的bucket,如果bucket的值是1,則認(rèn)為key存在。
Hash沖突的解決方案
布隆過濾器使用了Hash函數(shù),自然也逃不過Hash沖突的問題。對布隆過濾器而言,發(fā)生Hash沖突也就意味著會發(fā)生誤判。
傳統(tǒng)Hash算法解決Hash沖突的方式有開放定址法、鏈表法等。而布隆過濾器解決Hash沖突的方式比較特殊,它使用了多個Hash函數(shù)來解決沖突問題。
下圖中插入布隆過濾器的Bar和Baz經(jīng)過Hash2計算出的位置是同一個,但Hash3計算出的位置是不一樣的,Bar和Baz得以區(qū)分。
即使布隆過濾器使用了這種方式來解決Hash沖突,沖突的可能性依舊存在,如下圖所示:
由于布隆過濾器不保留插入的Key的原始值,Hash沖突是無法避免的。我們只能通過增加Hash函數(shù)的數(shù)量來減少沖突的概率,也就是減少誤判率。
假設(shè)布隆過濾器有m個bucket,包含k個哈希函數(shù),已經(jīng)插入了n個key。經(jīng)數(shù)學(xué)推導(dǎo)可得誤判率的公式如下:
具體推斷過程可參考/wiki/Bloom_filter。
布隆過濾器的誤判概率大致和已經(jīng)插入的key的數(shù)量n成正比,和hash函數(shù)數(shù)量k、bucket數(shù)m成反比。為了減少誤判率,我們可以增加m或增加k,增加m意味著過濾器占用存儲空間會增加,增加k則意味著插入和查詢時的效率會降低。
為什么布隆過濾器不支持刪除
布隆過濾器通過多個Hash函數(shù)來解決沖突的設(shè)計,也意味著多著插入元素可能會共享同樣的bucket,刪掉一個元素的同時,也會被其他元素的一部分bucket給刪掉。因此基于Bitmap實現(xiàn)的布隆過濾器是不支持刪除的。
用C#實現(xiàn)Bitmap
在實現(xiàn)布隆過濾器之前,我們首先要實現(xiàn)一個Bitmap。
在C#中,我們并不能直接用bit作為最小的數(shù)據(jù)存儲單元,但借助位運算的話,我們就可以基于其他數(shù)據(jù)類型來表示,比如byte。下文用byte作為例子來描述Bitmap的實現(xiàn),但不僅限于byte,int、long等等也是可以的。
位運算
下面是C#中位運算的簡單介紹:
符號描述運算規(guī)則與兩個位都為1時,結(jié)果才為1|或兩個位都為0時,結(jié)果才為0^異或兩個位相同為0,相異為1~取反0變1,1變0左移各二進位全部左移若干位,低位補0右移各二進位全部右移若干位,高位補0
一般來說,我們要進行位運算計算的數(shù)據(jù)通常都是由多個二進位組成的。對兩個數(shù)字使用、|、^這三個運算符時,需要對齊兩個數(shù)字的右邊,一位位地進行計算。
//0b代表值用二進制表示數(shù)字
shorta=0b0111111111111001;
byteb=0b011111111;
shortc=(short)(ab);//0b0111111111111001
shortd=(short)(a|b);//0b0111111111111111
shorte=(short)(a^b);//0b0000000000000110
bytef=(byte)~b;0b011111111;
shortg=(short)(b1);//0b0000000111111111;
shorth=(short)(b1);//0b0000000001111111;
利用位運算創(chuàng)建Bitmap
借助byte實現(xiàn)Bitmap,也就是要能夠修改和查看byte上的每一個bit的值,同時,修改要能夠?qū)崿F(xiàn)冪等。
1.指定位設(shè)置成1
按前面說的位運算的規(guī)則,是不能夠單獨修改bit序列中某一位的。位運算需要從右到左一對對計算。
使用|可以實現(xiàn)這個功能。假設(shè)我們要改變從右開始下標(biāo)為3(初始位置0)的bit的值,則需要準(zhǔn)備一個該位置為1,其他位置都是0的bit序列,與要改變的bit序列進行|運算。
//為了將a的右邊數(shù)起第3位改成1,需要準(zhǔn)備一個b
bytea=0b010100010;
byteb=13;//0b000001000
a|=b;//0b010101010
2.指定位設(shè)置成0
和設(shè)置成1正好相反,需要準(zhǔn)備一個指定位置為0,其他位置都是1的bit序列,與要改變的bit序列進行運算。
bytea=0b010101010;
byteb=13;//0b000001000
b=~b;//0b111110111
a=b;//0b010100010
3.查看指定位的值
利用運算符,只要計算結(jié)果不為0,就代表指定位置的值為1。
bytea=0b010101010;
byteb=13;//0b000001000;
a=b;//0b000001000;
了解了基本的操作之后,我們把數(shù)據(jù)存儲到byte數(shù)組上。
classBitmap
privatereadonlybyte[]_bytes;
privatereadonlylong_capacity;
publicBitmap(longcapacity)
_capacity=capacity;
_bytes=newbyte[_capacity/8+1];
publiclongCapacity=_capacity;
publicvoidSet(longindex)
if(index=_capacity)
thrownewIndexOutOfRangeException();
//計算出數(shù)據(jù)存在第幾個byte上
longbyteIndex=index/8;
//計算出數(shù)據(jù)存在第幾個bit上
intbitIndex=(int)(index%8);
_bytes[byteIndex]|=(byte)(1bitIndex);
publicvoidRemove(longindex)
if(index=_capacity)
thrownewIndexOutOfRangeException();
longbyteIndex=index/8;
intbitIndex=(int)(index%8);
_bytes[byteIndex]=(byte)~(1bitIndex);
publicboolGet(longindex)
if(index=_capacity)
thrownewIndexOutOfRangeException();
longbyteIndex=index/8;
intbitIndex=(int)(index%8);
return(_bytes[byteIndex](byte)(1bitIndex))!=0;
用C#實現(xiàn)布隆過濾器
有了Bitmap,我們再把Hash函數(shù)的實現(xiàn)準(zhǔn)備好,一個簡單的布隆過濾器就可以完成了。這里,我們參考guava這個java庫的實現(xiàn)。
/google/guava/blob/master/guava/src/com/google/common/hash/BloomFilter.java
MurmurHash3的使用
我們使用和guava一樣的MurmurHash3作為Hash函數(shù)的實現(xiàn)。
下面是筆者在github上找到的一個可用實現(xiàn)。
/darrenkopp/murmurhash-net
使用這個庫,我們可以將任意長的byte數(shù)組轉(zhuǎn)換成128位的二進制位,也就是16byte。
byte[]data=Guid.NewGuid().ToByteArray();
//returnsa128-bitalgorithmusing"unsafe"codewithdefaultseed
HashAlgorithmmurmur128=MurmurHash.Create128(managed:false);
byte[]hash=murmur128.ComputeHash(data);
將任意類型的key轉(zhuǎn)換為byte數(shù)組
Funnel與Sink的定義
我們需要將各種類型key轉(zhuǎn)換成MurmurHash能夠直接處理的byte數(shù)組。為此我們參考guava引入下面兩個概念:
1.Funnel:將各類數(shù)據(jù)轉(zhuǎn)換成byte數(shù)組,包括int、bool、string等built-in類型及自定義的復(fù)雜類型。
2.Sink:Funnel的核心組件,作為數(shù)據(jù)的緩沖區(qū)。Funnel在將自定義的復(fù)雜類型實例轉(zhuǎn)換成byte數(shù)組時,就需要將數(shù)據(jù)拆解分批寫入sink。
Funnel可以定義成如下的委托,接受原始值,并將其寫入sink中。
delegatevoidFunnelinT(Tfrom,ISinksink);
Sink將不同類型的數(shù)據(jù)轉(zhuǎn)換成byte數(shù)組并匯總到一起。
interfaceISink
ISinkPutByte(byteb);
ISinkPutBytes(byte[]bytes);
ISinkPutBool(boolb);
ISinkPutShort(shorts);
ISinkPutInt(inti);
ISinkPutString(strings,Encodingencoding);
ISinkPutObjectT(Tobj,FunnelTfunnel);
///...其他built-in類型,讀者可自行補充
簡單的Funnel實現(xiàn)如下所示:
publicclassFunnels
publicstaticFunnelstringStringFunnel=(from,sink)=
sink.PutString(from,Encoding.UTF8);
publicstaticFunnelintIntFunnel=(from,sink)=
sink.PutInt(from);
自定義復(fù)雜類型的Funnel實現(xiàn)則可以數(shù)據(jù)拆解分批寫入sink。復(fù)雜類型的實例成員依舊可能是復(fù)雜類型,因此我們要在Sink上實現(xiàn)一個PutObject來提供套娃式拆解。
FunnelFoofunnelFoo=(foo,sink)=
sink.PutString(foo.A,Encoding.UTF8);
sink.PutInt(foo.B);
FunnelBarfunnelBar=(bar,barSink)=barSink.PutBool(bar.C);
sink.PutObject(foo.Bar,funnelBar);
classFoo
publicstringA{get;set;}
publicintB{get;set;}
publicBarBar{get;set;}
classBar
publicboolC{get;set;}
Sink的實現(xiàn)
Sink的核心是byte數(shù)組緩沖區(qū)的實現(xiàn),利用ArrayPool我們可以很方便的實現(xiàn)一個ByteBuffer。
classByteBuffer:IDisposable
privatereadonlyint_capacity;
privatereadonlybyte[]_buffer;
privateint_offset;
privatebool_disposed;
publicByteBuffer(intcapacity)
_capacity=capacity;
_buffer=ArrayPoolbyte.Shared.Rent(capacity);
publicvoidPut(byteb)
CheckInsertable();
_buffer[_offset]=b;
_offset++;
publicvoidPut(byte[]bytes)
CheckInsertable();
bytes.CopyTo(_buffer.AsSpan(_offset,bytes.Length));
_offset+=bytes.Length;
publicvoidPutInt(inti)
CheckInsertable();
BinaryPrimitives.WriteInt32BigEndian(GetRemainingAsSpan(),i);
_offset+=sizeof(int);
publicvoidPutShort(shorts)
CheckInsertable();
BinaryPrimitives.WriteInt32BigEndian(GetRemainingAsSpan(),s);
_offset+=sizeof(short);
//...其他的primitivetype的實現(xiàn)
publicSpanbyteGetBuffer()=
_buffer.AsSpan(.._offset);
publicboolHasRemaining()=_offset_capacity;
publicvoidDispose()
_disposed=true;
ArrayPoolbyte.Shared.Return(_buffer);
privatevoidCheckInsertable()
if(_disposed)
thrownewObjectDisposedException(typeof(ByteBuffer).FullName);
if(_offset=_capacity)
thrownewOverflowException("Bytebufferoverflow");
privateSpanbyteGetRemainingAsSpan()=_buffer.AsSpan(_offset..);
Sink則是對ByteBuffer的進一步封裝,來適配當(dāng)前使用場景。
classSink:ISink,IDisposable
privatereadonlyByteBuffer_byteBuffer;
///summary
///創(chuàng)建一個新的seecref="Sink"/實例
////summary
///paramname="expectedInputSize"預(yù)計輸入的單個元素的最大大小/param
publicSink(intexpectedInputSize)
_byteBuffer=newByteBuffer(expectedInputSize);
publicISinkPutByte(byteb)
_byteBuffer.Put(b);
returnthis;
publicISinkPutBytes(byte[]bytes)
_byteBuffer.Put(bytes);
returnthis;
publicISinkPutBool(boolb)
_byteBuffer.Put((byte)(b1:0));
returnthis;
publicISinkPutShort(shorts)
_byteBuffer.PutShort(s);
returnthis;
publicISinkPutInt(inti)
_byteBuffer.PutInt(i);
returnthis;
publicISinkPutString(strings,Encodingencoding)
_byteBuffer.Put(encoding.GetBytes(s));
returnthis;
publicISinkPutObjectT(Tobj,FunnelTfunnel)
funnel(obj,this);
returnthis;
publicbyte[]GetBytes()=_byteBuffer.GetBuffer().ToArray();
publicvoidDispose()
_byteBuffer.Dispose();
k個Hash函數(shù)與布隆過濾器實現(xiàn)
上文提到了布隆過濾器通過k個hash函數(shù)來解決hash沖突問題。實踐中,我們可以把一次murmurhash的計算結(jié)果(16byte)拆分為兩部分并轉(zhuǎn)換為long類型(一個long是8byte)。
這兩部分結(jié)果分別保存到hash2和hash3,第k個hash函數(shù)是對hash2和hash3的重新組合。
hash(k)=hash2+(k-1)*hash3
publicclassBloomFilterT
privatereadonlyint_hashFunctions;
privatereadonlyFunnelT_funnel;
privatereadonlyint_expectedInputSize;
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 行政管理??茟?yīng)試策略試題及答案匯聚
- 2025年經(jīng)濟法概論備考材料及試題答案
- 衛(wèi)生資格考試熱點話題試題及答案揭曉
- 2025年執(zhí)業(yè)藥師與公眾健康的緊密聯(lián)系試題及答案
- 指導(dǎo)患者用藥的要點試題及答案
- 行政管理文化概論內(nèi)容的擴展與試題及答案總結(jié)
- 自考行政管理經(jīng)典試題及答案解析
- 護士執(zhí)業(yè)考試試題及答案深層研究
- 行政管理法律解析試題與答案
- 理解國粹的試題及答案
- 計算機三級《Linux應(yīng)用與開發(fā)技術(shù)》考試題庫大全(含真題、典型題等)
- 環(huán)境因素識別評價表
- 家長會課件:中考前百日誓師家長會課件
- 固腎生發(fā)丸的質(zhì)量控制和標(biāo)準(zhǔn)化
- 山東省濟南市槐蔭區(qū)2023-2024學(xué)年小學(xué)六年級語文畢業(yè)檢測指導(dǎo)卷含答案
- MOOC 音樂導(dǎo)聆-山東大學(xué) 中國大學(xué)慕課答案
- 農(nóng)行超級柜臺業(yè)務(wù)知識考試題庫(含答案)
- 農(nóng)產(chǎn)品加工工藝培訓(xùn)PPT創(chuàng)新農(nóng)產(chǎn)品加工工藝與技術(shù)
- 精神病患者藏藥的護理措施
- 提高中醫(yī)技術(shù)使用率品管圈課件
- 譯林版英語一年級下教學(xué)計劃各單元都有
評論
0/150
提交評論