1. 程式人生 > >布隆過濾器:Google Guava類庫原始碼分析及基於Redis Bitmaps的重構

布隆過濾器:Google Guava類庫原始碼分析及基於Redis Bitmaps的重構

本文源地址:http://www.fullstackyang.com/archives/464.html,轉發請註明該地址或伯樂線上地址,謝謝!

一、背景知識

在網上已經有很多關於布隆過濾器的介紹了,這裡就不再贅述,下面簡單地提煉幾個要點:

  1. 布隆過濾器是用來判斷一個元素是否出現在給定集合中的重要工具,具有快速,比雜湊表更節省空間等優點,而缺點在於有一定的誤識別率(false-positive,假陽性),亦即,它可能會把不是集合內的元素判定為存在於集合內,不過這樣的概率相當小,在大部分的生產環境中是可以接受的;
  2. 其原理比較簡單,如下圖所示,S集合中有n個元素,利用k個雜湊函式,將S中的每個元素對映到一個長度為m的位(bit)陣列B中不同的位置上,這些位置上的二進位制數均置為1,如果待檢測的元素經過這k個雜湊函式的對映後,發現其k個位置上的二進位制數不全是1,那麼這個元素一定不在集合S中,反之,該元素可能是S中的某一個元素(參考1);
  3. 綜上描述,那麼到底需要多少個雜湊函式,以及建立長度為多少的bit陣列比較合適,為了估算出k和m的值,在構造一個布隆過濾器時,需要傳入兩個引數,即可以接受的誤判率fpp和元素總個數n(不一定完全精確)。至於引數估計的方法,有興趣的同學可以參考維基英文頁面,下面直接給出公式:
  4. 雜湊函式的要求儘量滿足平均分佈,這樣既降低誤判發生的概率,又可以充分利用bit陣列的空間;
  5. 根據論文《Less Hashing, Same Performance: Building a Better Bloom Filter》提出的一個技巧,可以用2個雜湊函式來模擬k個雜湊函式,即gi(x) = h1(x) + ih2(x) ,其中0<=i<=k-1;
  6. 在吳軍博士的《數學之美》一書中展示了不同情況下的誤判率,例如,假定一個元素用16位位元,8個雜湊函式,那麼假陽性的概率是萬分之五,這已經相當小了。

目前已經有相應實現的開源類庫,如Google的Guava類庫,Twitter的Algebird類庫,和ScalaNLP breeze等等,其中Guava 11.0版本中增加了BloomFilter類,它使用了Funnel和Sink的設計,增強了泛化的能力,使其可以支援任何資料型別,其利用murmur3 hash來做雜湊對映函式,不過它底層並沒有使用傳統的java.util.BitSet來做bit陣列,而是用long型陣列進行了重新封裝,大部分操作均基於位的運算,因此能達到一個非常好的效能;下面我們就Guava類庫中實現布隆過濾器的原始碼作詳細分析,最後出於靈活性和解耦等因素的考慮,我們想要把布隆過濾器從JVM中拿出來,於是利用了Redis自帶的Bitmaps作為底層的bit陣列進行重構,另外隨著插入的元素越來越多,當實際數量遠遠大於建立時設定的預計數量時,布隆過濾器的誤判率會越來越高,因此在重構的過程中增加了自動擴容的特性,最後通過測試驗證其正確性。

二、布隆過濾器在Guava中的實現

Guava中,布隆過濾器的實現主要涉及到2個類,BloomFilter和BloomFilterStrategies,首先來看一下BloomFilter:

Java
12345678910111213 /** The bit set of the BloomFilter (not necessarily power of 2!) */privatefinalBitArray bits;/** Number of hashes per element */privatefinalintnumHashFunctions;/** The funnel to translate Ts to bytes */privatefinalFunnel<?superT>funnel;/**   * The strategy we employ to map an element T to {@ code numHashFunctions} bit indexes.   */privatefinalStrategy strategy;

這是它的4個成員變數

  • BitArrays是定義在BloomFilterStrategies中的內部類,封裝了布隆過濾器底層bit陣列的操作,後文詳述;
  • numHashFunctions表示雜湊函式的個數,即上文提到的k;
  • Funnel,這是Guava中定義的一個介面,它和PrimitiveSink配套使用,主要是把任意型別的資料轉化成Java基本資料型別(primitive value,如char,byte,int……),預設用java.nio.ByteBuffer實現,最終均轉化為byte陣列;
  • Strategy是定義在BloomFilter類內部的介面,程式碼如下,有3個方法,put(插入元素),mightContain(判定元素是否存在)和ordinal方法(可以理解為列舉類中那個預設方法)
Java
12345678910111213141516171819202122232425 interfaceStrategy extendsjava.io.Serializable{/**     * Sets {@ code numHashFunctions} bits of the given bit array, by hashing a user element.     *     * <p>Returns whether any bits changed as a result of this operation.     */<T>booleanput(Tobject,Funnel<?superT>funnel,intnumHashFunctions,BitArray bits);/**     * Queries {@ code numHashFunctions} bits of the given bit array, by hashing a user element;     * returns {@ code true} if and only if all selected bits are set.     */<T>booleanmightContain(Tobject,Funnel<?superT>funnel,intnumHashFunctions,BitArray bits);/**     * Identifier used to encode this strategy, when marshalled as part of a BloomFilter. Only     * values in the [-128, 127] range are valid for the compact serial form. Non-negative values     * are reserved for enums defined in BloomFilterStrategies; negative values are reserved for any     * custom, stateful strategy we may define (e.g. any kind of strategy that would depend on user     * input).     */intordinal();}

對於建立布隆過濾器,BloomFilter並沒有公有的建構函式,只有一個私有建構函式,而對外它提供了5個過載的create方法,在預設情況下誤判率設定為3%,採用BloomFilterStrategies.MURMUR128_MITZ_64的實現。其中4個create方法最終都呼叫了同一個create方法,由它來負責呼叫私有建構函式,其原始碼如下:

Java
12345678910111213141516171819202122232425 static<T>BloomFilter<T>create(Funnel<?superT>funnel,longexpectedInsertions,doublefpp,Strategy strategy){checkNotNull(funnel);checkArgument(expectedInsertions>=0,"Expected insertions (%s) must be >= 0",expectedInsertions);checkArgument(fpp>0.0,"False positive probability (%s) must be > 0.0",fpp);checkArgument(fpp<1.0,"False positive probability (%s) must be < 1.0",fpp);checkNotNull(strategy);if(expectedInsertions==0){expectedInsertions=1;}/*     * TODO(user): Put a warning in the javadoc about tiny fpp values, since the resulting size     * is proportional to -log(p), but there is not much of a point after all, e.g.     * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares!     */longnumBits=optimalNumOfBits(expectedInsertions,fpp);intnumHashFunctions=optimalNumOfHashFunctions(expectedInsertions,numBits);try{returnnewBloomFilter<T>(newBitArray(numBits),numHashFunctions,funnel,strategy);}catch(IllegalArgumentExceptione){thrownewIllegalArgumentException("Could not create BloomFilter of "+numBits+" bits",e);}}

在create中接受了4個引數,funnel(輸入的資料),expectedInsertions(預計插入的元素總數),fpp(期望誤判率),strategy(實現Strategy的例項),然後它計算了bit陣列的長度以及雜湊函式的個數(公式參考前文),最後用numBits建立了BitArray,並呼叫了建構函式完成賦值操作。

Java
1234567891011 staticlongoptimalNumOfBits(longn,doublep){if(p==0){p=Double.MIN_VALUE;}return(long)(-n*Math.log(p)/(Math.log(2)*Math.log(2)));}staticintoptimalNumOfHashFunctions(longn,longm){// (m / n) * log(2), but avoid truncation due to division!returnMath.max(1,(int)Math.round((double)m/n*Math.log(2)));}
接著再來看一下BloomFilterStrategies類,首先它是實現了BloomFilter.Strategy 介面的一個列舉類,其次它有兩個2列舉值,MURMUR128_MITZ_32和MURMUR128_MITZ_64,分別對應了32位雜湊對映函式,和64位雜湊對映函式,後者使用了murmur3 hash生成的所有128位,具有更大的空間,不過原理是相通的,我們選擇預設的MURMUR128_MITZ_64來分析: Java
12345678910111213141516171819202122232425262728293031323334353637 MURMUR128_MITZ_64(){<ahref='http://www.jobbole.com/members/wx610506454'>@Override</a>public<T>booleanput(Tobject,Funnel<?superT>funnel,intnumHashFunctions,BitArray bits){longbitSize=bits.bitSize();byte[]bytes=Hashing.murmur3_128().hashObject(object,funnel).getBytesInternal();longhash1=lowerEight(bytes);longhash2=upperEight(bytes);booleanbitsChanged=false;longcombinedHash=hash1;for(inti=0;i<numHashFunctions;i++){// Make the combined hash positive and indexablebitsChanged|=bits.set((combinedHash&Long.MAX_VALUE)%bitSize);combinedHash+=hash2;}returnbitsChanged;}@Overridepublic<T>booleanmightContain(Tobject,Funnel<?superT>funnel,intnumHashFunctions,BitArray bits){longbitSize=bits.bitSize();byte[]bytes=Hashing.murmur3_128().hashObject(object,funnel).getBytesInternal();longhash1=lowerEight(bytes);longhash2=upperEight(bytes);longcombinedHash=hash1;for(inti=0;i<numHashFunctions;i++){// Make the combined hash positive and indexableif(!bits.get((combinedHash&Long.MAX_VALUE)%bitSize)){returnfalse;}combinedHash+=hash2;}returntrue;}

抽象來看,put是寫,mightContain是讀,兩個方法的程式碼有一點相似,都是先利用murmur3 hash對輸入的funnel計算得到128位的位元組陣列,然後高低分別取8個位元組(64位)建立2個long型整數hash1,hash2作為雜湊值。迴圈體內採用了2個函式模擬其他函式的思想,即上文提到的gi(x) = h1(x) + ih2(x) ,這相當於每次累加hash2,然後通過基於bitSize取模的方式在bit陣列中索引。

這裡之所以要和Long.MAX_VALUE進行按位與的操作,是因為在除數和被除數符號不一致的情況下計算所得的結果是有差別的,在程式語言裡,“%”準確來說是取餘運算(C,C++和Java均如此,python是取模),如-5%3=-2,而取模的數學定義是x mod y=x-y[x/y](向下取整),所以-5 mod 3= -5-3*(-2)=1,因此當雜湊值為負數的時候,其取餘的結果為負(bitSize始終為正數),這樣就不方便在bit陣列中取值,因此通過Long.MAX_VALUE(二進位制為0111…1111),直接將開頭的符號位去掉,從而轉變為正數。當然也可以取絕對值,在另一個MURMUR128_MITZ_32的實現中就是這麼做的。

在put方法中,先是將索引位置上的二進位制置為1,然後用bitsChanged記錄插入結果,如果返回true表明沒有重複插入成功,而mightContain方法則是將索引位置上的數值取出,並判斷是否為0,只要其中出現一個0,那麼立即判斷為不存在。

最後再說一下底層bit陣列的實現,主要程式碼如下:

Java
123456789101112131415161718192021222324252627282930313233343536373839 staticfinalclassBitArray{

相關推薦

過濾器:Google Guava原始碼分析基於Redis Bitmaps重構

本文源地址:http://www.fullstackyang.com/archives/464.html,轉發請註明該地址或伯樂線上地址,謝謝! 一、背景知識 在網上已經有很多關於布隆過濾器的介紹了,這裡就不再贅述,下面簡單地提煉幾個要點: 布隆過濾器是用來判斷一

Google Guava 簡介

math dir 多好 spa 裝飾 ava collect mon 集合接口   Guava 是一個 Google開發的 基於java的類庫集合的擴展項目,包括 collections, caching, primitives support, concurrency l

刁肥宅資料結構課設“過濾器的實踐與應用”原始碼(v1.0,永不上交)

       程式碼很簡單,寫了一些註釋;加上註釋看就很清楚了。        檔案bloomfilter.cpp: #include "bloomfilter.h" // return a hash range from 0 to 79999 int hash(con

jdk原始碼分析-各個包

這幾天分析了幾個類的原始碼,有了很多的思路,對於一些演算法和資料結構有了一定了解。但是,我對於各個類包還是迷糊,這裡補充下,讓大家有個清晰的認識。 這裡我主要以jdk1.6版本為例,現在版本已經是1.9了,但是實際開發的時候有很多問題暫時不說。 Java類庫

中文情感分析——snownlp 原始碼註釋使用

最近發現了snownlp這個庫,這個類庫是專門針對中文文字進行文字挖掘的。 主要功能: 中文分詞(Character-Based Generative Model) 詞性標註(TnT 3-gram 隱馬) 情感分析(現在訓練資料主要是買賣東西時的評價,所以對其他的一些可能效果不是很好,待

過濾器(Bloom Filters)的原理程式碼實現(Python + Java)

本文介紹了布隆過濾器的概念及變體,這種描述非常適合程式碼模擬實現。重點在於標準布隆過濾器和計算布隆過濾器,其他的大都在此基礎上優化。文末附上了標準布隆過濾器和計算布隆過濾器的程式碼實現(Java版和Python版) 本文內容皆來自 《Foundations of Computers Systems Rese

GoogleGuava簡介

Guava是一個Google的基於Java的類庫集合的擴充套件專案,包括collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O等等,

BloomFilter(過濾器)原理和python支援

簡介   Bloom Filter(布隆過濾器)是一種多雜湊函式對映的快速查詢演算法。通常應用在需要快速判斷一個元素是否屬於集合,但是並不是嚴格要求100%正確的場合。   即Bloom Filter是會誤判的,但是它只會把不存在於集合中的元素誤判成存在

第三百五十八節,Python分式爬蟲打造搜索引擎Scrapy精講—將bloomfilter(過濾器)集成到scrapy-redis

分布式爬蟲 times 操作 加載 ger 目錄 需要 ini space 第三百五十八節,Python分布式爬蟲打造搜索引擎Scrapy精講—將bloomfilter(布隆過濾器)集成到scrapy-redis中,判斷URL是否重復 布隆過濾器(Bloom Filte

BloomFilter(過濾器)

但是 同時 see oom font 關聯 代碼實現 filter func 原文鏈接:http://blog.csdn.net/qq_38646470/article/details/79431659 1.概念: 如果想判斷一個元素是不是在一個集合裏,一般想到的是將所有元

Bloom Filter過濾器

布隆過濾器 數據及結構 原文鏈接:http://blog.csdn.net/qq_38646470/article/details/794316591.概念:如果想判斷一個元素是不是在一個集合裏,一般想到的是將所有元素保存起來,然後通過比較確定。鏈表,樹等等數據結構都是這種思路. 但是隨著集合中元素的

Bloom filter(過濾器)概念與原理

概念 int 復雜 gravity water pac 基數 AS class https://en.wikipedia.org/wiki/Bloom_filter 寫在前面 在大數據與雲計算發展的時代,我們經常會碰到這樣的問題。我們是否能高效的判斷一個用

使用MR編程hbase和hbase調優-過濾器

style zook 存儲空間 del 使用 字節數 permsize 判斷 enabled 使用MR編程操縱hbase ====================================== 1、TableInputFormat輸入K,V格式

使用google-gson解析json文件

exception 輸入 json對象 bsp main arr class jsonarray bject 使用google-gson類庫解析json文件 使用JsonParser解析器來解析字符串和輸入流,變成json對象 代碼如下: public class Rea

過濾器的方式解決緩存穿透問題

元素 分享 http enc 輸出結果 思路 支持 負載 根據 1、原理 布隆過濾器的巨大用處就是,能夠迅速判斷一個元素是否在一個集合中。因此他有如下三個使用場景: 網頁爬蟲對URL的去重,避免爬取相同的URL地址 反垃圾郵件,從數十億個垃圾郵件列表中

過濾器之Python+Redis

ini ros 如果 函數的參數 cccccc pytho 變化 ear ember 簡單的python實現 pip install mmh3 對於安裝報錯,c++編譯錯誤問題:可以安裝 Microsoft Visual C++ Build Tools()

淺談過濾器

1. 問題情景 如果面試官問你,一個網站有 100 億 url 存在一個黑名單中,每條 url 平均 64 位元組。問這個黑名單要怎麼存?若此時隨便輸入一個 url,如何判斷該 url 是否在這個黑名單中? 對於第一個問題,如果把黑名單看成一個集合,將其存在 hashmap 中,貌似太大了,需要 640G

大量資料去重:Bitmap點陣圖演算法和過濾器(Bloom Filter)

Bitmap演算法 與其說是演算法,不如說是一種緊湊的資料儲存結構。是用記憶體中連續的二進位制位(bit),用於對大量整型資料做去重和查詢。其實如果並非如此大量的資料,有很多排重方案可以使用,典型的就是雜湊表。 實際上,雜湊表為每一個可能出現的數字提供了一個一一對映的關係,每個元素都相當於有

過濾器go實現

1 布隆過濾器原理 布隆過濾器一般用來判斷一個數據是否在一個很大的資料集合裡面。當然可以用陣列,集合,樹等資料結構和各種查詢法都可以做同樣的事情,但是布隆過濾器有更好的時間效率和空間效率。比特幣實現SPV節點時使用了布隆過濾器來查詢交易。布隆過濾器可以判斷一個數在不在集合裡,但存在一定的誤判率。

雜湊擴充套件——過濾器

一、基本原理:      對於原理來說很簡單,位陣列+k個獨立hash函式。將hash函式對應的值的位陣列置1,查詢時如果發現所有hash函式對應位都是1說明存在,但是這個過程並不能保證查詢的結果是100%正確的。 二、要點: 刪除 &nb