[toc] 1. 布隆過濾器的概念 布隆過濾器(Bloom Filter) 是由 Howard Bloom在1970年提出的 ,它具有很好的 ,被用來 ,即判定 兩種情況。如果檢測結果為是,該元素不一定在集合中;但如果檢測結果為否,該元素一定不在集合中,因此Bloom filter 。 2. 布隆過 ...
目錄
1. 布隆過濾器的概念
布隆過濾器(Bloom Filter) 是由 Howard Bloom在1970年提出的二進位向量數據結構
,它具有很好的空間和時間效率
,被用來檢測一個元素是不是集合中的一個成員
,即判定 “可能已存在和絕對不存在”
兩種情況。如果檢測結果為是,該元素不一定在集合中;但如果檢測結果為否,該元素一定不在集合中,因此Bloom filter具有100%的召回率
。
2. 布隆過濾器應用場景
- 垃圾郵件過濾
- 防止緩存擊穿
- 比特幣交易查詢
- 爬蟲的URL過濾
- IP黑名單
- 查詢加速【比如基於KV結構的數據】
- 集合元素重覆的判斷
3. 布隆過濾器工作原理
布隆過濾器的核心是一個超大的位數組
和幾個哈希函數
。假設位數組的長度為m,哈希函數的個數為k。
下圖表示有三個hash函數,比如一個集合中有x,y,z三個元素,分別用三個hash函數映射到二進位序列的某些位上,假設我們判斷w是否在集合中,同樣用三個hash函數來映射,結果發現取得的結果不全為1,則表示w不在集合裡面。
工作流程:
- 第一步:開闢空間:
開闢一個長度為m的位數組(或者稱二進位向量),這個不同的語言有不同的實現方式,甚至你可以用文件來實現。 - 第二步:尋找hash函數
獲取幾個hash函數,前輩們已經發明瞭很多運行良好的hash函數,比如BKDRHash,JSHash,RSHash等等。這些hash函數我們直接獲取就可以了。 - 第三步:寫入數據
將所需要判斷的內容經過這些hash函數計算,得到幾個值,比如用3個hash函數,得到值分別是1000,2000,3000。之後設置m位數組的第1000,2000,3000位的值位二進位1。 - 第四步:判斷
接下來就可以判斷一個新的內容是不是在我們的集合中。判斷的流程和寫入的流程是一致的。
4. 布隆過濾器的優缺點
1、優點:
- 有很好的
空間和時間效率
存儲空間和插入/查詢時間都是常數
。- Hash函數相互之間沒有關係,方便由硬體並行實現。
- 不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優勢。
- 布隆過濾器可以表示
全集
,其它任何數據結構都不能。
2、缺點:
誤判率會隨元素的增加而增加
不能從布隆過濾器中刪除元素
5. 布隆過濾器註意事項
布隆過濾器思路比較簡單,但是對於布隆過濾器的隨機映射函數設計,需要計算幾次,向量長度設置為多少比較合適,這個才是需要認真討論的。
如果向量長度太短,會導致誤判率直線上升。
如果向量太長,會浪費大量記憶體。
如果計算次數過多,會占用計算資源,且很容易很快就把過濾器填滿。
6. Go實現布隆過濾器
1. 開源包簡單演示
package main
import (
"fmt"
"github.com/willf/bitset"
"math/rand"
)
func main() {
Foo()
bar()
}
func Foo() {
var b bitset.BitSet // 定義一個BitSet對象
b.Set(1).Set(2).Set(3) //添加3個元素
if b.Test(2) {
fmt.Println("2已經存在")
}
fmt.Println("總數:", b.Count())
b.Clear(2)
if !b.Test(2) {
fmt.Println("2不存在")
}
fmt.Println("總數:", b.Count())
}
func bar() {
fmt.Printf("Hello from BitSet!\n")
var b bitset.BitSet
// play some Go Fish
for i := 0; i < 100; i++ {
card1 := uint(rand.Intn(52))
card2 := uint(rand.Intn(52))
b.Set(card1)
if b.Test(card2) {
fmt.Println("Go Fish!")
}
b.Clear(card1)
}
// Chaining
b.Set(10).Set(11)
for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) {
fmt.Println("The following bit is set:", i)
}
// 交集
if b.Intersection(bitset.New(100).Set(10)).Count() == 1 {
fmt.Println("Intersection works.")
} else {
fmt.Println("Intersection doesn't work???")
}
}
2. 封裝的方法:
//----------------------------------------------------------------------------
// @ Copyright (C) free license,without warranty of any kind .
// @ Author: hollson <[email protected]>
// @ Date: 2019-12-06
// @ Version: 1.0.0
//------------------------------------------------------------------------------
package bloomx
import "github.com/willf/bitset"
const DEFAULT_SIZE = 2<<24
var seeds = []uint{7, 11, 13, 31, 37, 61}
type BloomFilter struct {
Set *bitset.BitSet
Funcs [6]SimpleHash
}
func NewBloomFilter() *BloomFilter {
bf := new(BloomFilter)
for i:=0;i< len(bf.Funcs);i++{
bf.Funcs[i] = SimpleHash{DEFAULT_SIZE,seeds[i]}
}
bf.Set = bitset.New(DEFAULT_SIZE)
return bf
}
func (bf BloomFilter) Add(value string){
for _,f:=range(bf.Funcs){
bf.Set.Set(f.hash(value))
}
}
func (bf BloomFilter) Contains(value string) bool {
if value == "" {
return false
}
ret := true
for _,f:=range(bf.Funcs){
ret = ret && bf.Set.Test(f.hash(value))
}
return ret
}
type SimpleHash struct{
Cap uint
Seed uint
}
func (s SimpleHash) hash(value string) uint{
var result uint = 0
for i:=0;i< len(value);i++{
result = result*s.Seed+uint(value[i])
}
return (s.Cap-1)&result
}
func main() {
filter := bloomx.NewBloomFilter()
fmt.Println(filter.Funcs[1].Seed)
str1 := "hello,bloom filter!"
filter.Add(str1)
str2 := "A happy day"
filter.Add(str2)
str3 := "Greate wall"
filter.Add(str3)
fmt.Println(filter.Set.Count())
fmt.Println(filter.Contains(str1))
fmt.Println(filter.Contains(str2))
fmt.Println(filter.Contains(str3))
fmt.Println(filter.Contains("blockchain technology"))
}
100W數量級下布隆過濾器測試,源碼可參考https://download.csdn.net/download/Gusand/12018239
參考:
推薦:https://www.cnblogs.com/z941030/p/9218356.html
https://www.jianshu.com/p/01309d298a0e
https://www.cnblogs.com/zengdan-develpoer/p/4425167.html
https://blog.csdn.net/liuzhijun301/article/details/83040178
https://github.com/willf/bloom