簡介
布隆過濾器可以說是在大數據的處理算法方面經常使用的基礎算法。
在這方面我看了很多的博客,確實看到了很多很詳細的解釋和總結,但是都是零散的,沒有很全面的在原理和實現,以及實現代碼的解析等方面做的很全面的。所以我將我自己整理的東西很完整的和大家分享。
其中在實際的使用和實現方面,我會增加spark的實現,以及scala的BF和CBF的兩個簡單的demo。
BloomFilter
使用範圍:可以用來實現數據字典,進行數據的判重,或者集合求交
原理:位數組+k個獨立hash函數。將hash函數對應的值的位數組置1,查找時如果發現所有hash函數對應位都是1說明存在,很明顯這個過程並不保證查找的結果是100%正確的。
缺點:首先就是會存在錯誤率,但是爲什麼有錯誤率還是仍然被大量使用呢?這個也很簡單理解,畢竟在真正的業務場景中那可以處理上十億條數據,那麼假如說有0.001的錯誤率那看在時間高效的優點下,還是會選擇BF的。同時也不支持刪除一個已經插入的關鍵字,因爲修改關鍵字對應的位會牽動到其他的關鍵字。
上面的缺點我們提到了就是存在錯誤率,那麼好消息是這個錯誤率其實是可以被開發人員根據應用場景的要求來調整的。
那麼上面我們解釋一下參數的意思:
p代表錯誤率,一般設置的參數0.01或者更小。
n是輸入的元素的個數。
m代表bit數組長度。
然後k代表hash函數的個數。
舉個例子我們假設錯誤率爲0.01,則此時m應大概是n的13倍。這樣k大概是8個。
通常單個元素的長度都是有很多bit的。所以使用bloom filter內存上通常都是節省的。
可見我們能得到一個規律:
那就是你想要的錯誤率越低,m就需要的位數越大。
然後m越大就需要的hash函數的個數越多。
仔細一想 沒毛病。當然時間也會越長,但是和其他的遍歷相等的方法也快了不止一點半點。
BloomFilter的簡單優化
我們知道只要你使用了BloomFilter就會存在一點點的錯誤率,那麼既然你使用布隆過濾器來加速查找和判斷是否存在,那麼性能很低的哈希函數不是個好選擇,推薦 MurmurHash這類的高性能hash函數。
在後面的代碼部分我會實現一個scala的使用MurmurHash的BloomFilter。
改進BloomFilter
Bloom filter將集合中的元素映射到位數組中,用k(k爲哈希函數個數)個映射位是否全1表示元素在不在這個集合中。Counting bloom filter(CBF)將位數組中的每一位擴展爲一個counter,從而支持了元素的刪除操作。Spectral Bloom Filter(SBF)將其與集合元素的出現次數關聯。SBF採用counter中的最小值來近似表示元素的出現頻率。
spark 的布隆過濾器
其實spark框架下有很好的封裝,所以即使你不知道原理,也可以使用。
bloomFilter(colName:String,expectedNumItems:Long,fpp:Double)
//param(使用的數據列,數據量期望,損失精度)
//損失精度越低生成的布隆數組長度越長,佔用的空間越多,計算過程越長。
然後我用scala實現了一個spark中的BF
import day0215.schema_info
import org.apache.spark.sql.SparkSession
/**
* @Author: Braylon
* @Date: 2020/2/18 17:02
* @Description: BF in Spark
*/
object BloomFilter {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().master("local").appName("spark sql2").getOrCreate()
val data = spark.sparkContext.textFile("D:\\idea\\projects\\scalaDemo\\src\\resources\\node.txt").map(_.split(" "))
val df_ = data.map(s => schema_info(s(0).toInt, s(1).trim(), s(2).toInt))
import spark.sqlContext.implicits._
var df = df_.toDF
df.show()
val df1 = spark.sparkContext.parallelize(Seq(
Worker("Braylon",30000), Worker("Tim",1000), Worker("Jackson",20000)
)).toDF
df1.show(false)
val rdd = spark.sparkContext.parallelize(Seq("Braylon","J.C","Neo"))
// 生成bloomFilter
val bf = df1.stat.bloomFilter("name",20L,0.01)
val res = rdd.map(x=>(x,bf.mightContainString(x)))
res.foreach(println)
}
}
case class Worker(name:String,Sal:Int)
scala實現BF、CBF
package BigDataAlgorithm
import java.util.BitSet
import scala.util.hashing.MurmurHash3
/**
* @Author: Braylon
* @Date: 2020/2/19 10:44
* @Description: scala BF
*/
object BloomFilter {
//定義映射位數組長度
val BitArr = 1 << 16
//定義Bit數組
val bitSet = new BitSet(BitArr)
/**
* 一個名爲seed的值代表鹽。向其提供任何隨機但私有的(對您的應用而言)數據,因此哈希函數將爲相同數據提供不同的結果。
* 例如,使用此功能提取您的數據摘要,以檢測第三人對原始數據的修改。在知道您使用的鹽之前,他們幾乎無法複製有效的哈希值。
* final def stringHash(str: String, seed: Int): Int
* Compute the hash of a string
*/
val seed = 2
def hash(str:String):Unit = {
//null是用來判斷有沒有這個容器,而isEmpty是有這個容器,來判斷這個容器中的內容有沒有東西是不是空的
if (str != null && !str.isEmpty) {
for (seed_tmp <- 1 to seed) bitSet.set(Math.abs(MurmurHash3.stringHash(str, seed_tmp)) % BitArr, true)
}
else
println("error input")
}
/**
* 判斷存在
*/
def checkIfExisted(str:String):Boolean = {
def existsRecur(str: String,seed_tmp:Int):Boolean = {
if (str == null && str.isEmpty) false
else if (seed_tmp > seed) true
else if (!bitSet.get(Math.abs(MurmurHash3.stringHash(str, seed_tmp)) % BitArr)) false
/**
* boolean get(int index)
* 返回指定索引處的位值。
*/
else existsRecur(str, seed + 1)
}
if (str == null || str.isEmpty)
false
else
existsRecur(str, 1)
}
def main(args: Array[String]): Unit = {
val str1 = "timeStamp"
val str2 = "what are you up to"
val str3 = "back to WHU as soon as possible"
val str4 = "i love WHU"
BloomFilter.hash(str1)
BloomFilter.hash(str2)
BloomFilter.hash(str3)
BloomFilter.hash(str4)
println(BloomFilter.checkIfExisted(str1))
println(BloomFilter.checkIfExisted(str3))
println(BloomFilter.checkIfExisted("neo"))
println(BloomFilter.checkIfExisted("ksjdfhwiebxdkjfskf"))
}
}
CBF
package BigDataAlgorithm
import scala.util.hashing.MurmurHash3
/**
* @Author: Braylon
* @Date: 2020/2/19 12:08
* @Description: scala CBF
*/
object CountingBloomFilter {
val totalNum = 1 << 16
val CBFArr = new Array[Int](totalNum)
//我們仍然使用MurmurHash,定義seedNum
val seedNum = 2
def hash(str:String):Unit = {
if (str != null && !str.isEmpty){
for (seed_tmp <- 1 to seedNum) CBFArr(Math.abs(MurmurHash3.stringHash(str, seed_tmp)) % totalNum) += 1
}
else
println("error input")
}
def checkIfExisted(str:String):Boolean = {
def existed(str:String, seed_tmp:Int):Boolean = {
if (str == null && str.isEmpty) false
else if (seed_tmp > seedNum) true
else if (CBFArr(Math.abs(MurmurHash3.stringHash(str, seed_tmp)) % totalNum) < 0 ) false
/**
* boolean get(int index)
* 返回指定索引處的位值。
*/
else existed(str, seed_tmp + 1)
}
if (str==null || str.isEmpty) false
else existed(str, 1)
}
def main(args: Array[String]): Unit = {
val str1 = "www.baidu.com"
val str2 = "Tom and Jerry"
val str3 = "Tim is a good girl"
val str4 = "Braylon is a good student"
BloomFilter.hash(str1)
BloomFilter.hash(str2)
BloomFilter.hash(str3)
BloomFilter.hash(str4)
println(BloomFilter.checkIfExisted(str1))
println(BloomFilter.checkIfExisted(str3))
println(BloomFilter.checkIfExisted("neo"))
println(BloomFilter.checkIfExisted("ksjdfhwiebxdkjfskf"))
}
}
大家共勉~~
有不好的地方歡迎指正