持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
Go 布隆过滤器使用
判断目标值是否在一个集合中,我们会想到一个 HashMap 实现,但是当集合存储数据比较多时,此时,map 会消耗大量的内存,此时可能会考虑 bitmap,但是 bitmap 只能保存整型数据,对于字符串类型,除非把字符串映射成整型数据。
布隆过滤器
布隆过滤器是布隆(Burton Howard Bloom)在1970年提出的,布隆过滤器由一个 BitArray(长度为m的位数组)和k个哈希函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率。
所谓错误识别率是指:如果Bloom Filter 报告元素不存在,那么该元素一定不在集合中, 如果 Bloom Fliter 判断存在,元素可能不在集合中。
使用场景
在缓存穿透,垃圾邮件识别可能有比较好的效果。
算法说明
-
长度为m 的位数组初始化值为0, hash 函数应该是尽量把数据均匀散列
-
插入一个元素,将元素数据分别输入 K 个hash 函数,产生 K 个 hash 值,以 hash 值作为 bit数组下标。将下标对应位置记录为1
-
当查询数据时,将元素输入 hash 函数,然后查询对应的 K 个 bit位,如果有任意一个bit 位置 为0 ,说明该元素一定不在集合中,如果比特位置都为1 ,说明该元素有较大可能性存在集合中。
代码实现
package bloom
import (
"fmt"
"testing"
"github.com/bits-and-blooms/bitset"
)
//设置哈希数组默认大小为16
const DefaultSize = 16
//设置种子,保证不同哈希函数有不同的计算方式
var seeds = []uint{7, 11, 13, 31, 37, 61}
//布隆过滤器结构,包括二进制数组和多个哈希函数
type BloomFilter struct {
//使用第三方库
set *bitset.BitSet
//指定长度为6
hashFuncs [6]func(seed uint, value string) uint
}
//构造一个布隆过滤器,包括数组和哈希函数的初始化
func NewBloomFilter() *BloomFilter {
bf := new(BloomFilter)
// 长度16默认bit array
bf.set = bitset.New(DefaultSize)
// 构造 6 个 hash 函数
for i := 0; i < len(bf.hashFuncs); i++ {
bf.hashFuncs[i] = createHash()
}
return bf
}
//构造6个哈希函数,每个哈希函数有参数seed保证计算方式的不同
func createHash() func(seed uint, value string) uint {
return func(seed uint, value string) uint {
var result uint = 0
for i := 0; i < len(value); i++ {
result = result*seed + uint(value[i])
}
//length = 2^n 时,X % length = X & (length - 1)
return result & (DefaultSize - 1)
}
}
//添加元素
func (b *BloomFilter) add(value string) {
for i, f := range b.hashFuncs {
//将哈希函数计算结果对应的数组位置1
b.set.Set(f(seeds[i], value))
}
}
// 查询元素是否存在
func (b *BloomFilter) contains(value string) bool {
//调用每个哈希函数,并且判断数组对应位是否为1
//如果不为1,直接返回false,表明一定不存在
for i, f := range b.hashFuncs {
//result = result && b.set.Test(f(seeds[i], value))
if !b.set.Test(f(seeds[i], value)) {
return false
}
}
return true
}
func TestBloomFilter(t *testing.T) {
filter := NewBloomFilter()
filter.add("hello")
filter.add("world")
fmt.Println(filter.contains("hello"))
fmt.Println(filter.contains("world"))
fmt.Println(filter.contains("xiao"))
fmt.Println(filter.contains("xiaoming")) // 出现误判
}
测试结果:
=== RUN TestBloomFilter
true
true
false
true
--- PASS: TestBloomFilter (0.00s)
PASS