概述
关于布隆过滤器的概念性的介绍,我就不多做解释了,可以详细查看一下文章最后的那篇参考资料。
我实现这个布隆过滤器是从三个公式开始的,如下图所示
只有先处理好这些布隆过滤器的参数以后,才方便创建一个布隆过滤器。三个计算公式对应的实现代码如下:
// 计算布隆过滤器位图大小
// elemNum 元素个数
// errorRate 误判率
func CalBloomSize(elemNum uint64, errRate float64) uint64 {
var bloomBitsSize = float64(elemNum)*math.Log(errRate)/(math.Log(2)*math.Log(2))*(-1)
return uint64(math.Ceil(bloomBitsSize))
}
// 计算需要的哈希函数数量
// elemNum 元素个数
// bloomSize 布隆过滤器位图大小,单位bit
func CalHashFuncNum(elemNum, bloomSize uint64) uint64 {
var k = math.Log(2)*float64(bloomSize)/float64(elemNum)
return uint64(math.Ceil(k))
}
//计算布隆过滤器误判率
// elemNum 元素个数
// bloomSize 布隆过滤器位图大小,单位bit
// hashFuncNum 哈希函数个数
func CalErrRate(elemNum, bloomSize, hashFuncNum uint64) float64 {
var y = float64(elemNum)*float64(hashFuncNum)/float64(bloomSize)
return math.Pow(float64(1)-math.Pow(math.E, y*float64(-1)), float64(hashFuncNum))
}
下面我们开始定义布隆过滤器的类型
type Filter struct {
ElemNum uint64
BloomSize uint64 //单位bit
HashFuncNum uint64
ErrRate float64
bitMap *bitset.BitSet
keys map[uint32]bool
}
ElemNum表示布隆过滤器要加入的元素数量
BloomSize表示布隆过滤器使用的bitmap大小,单位是bit
HashFuncNum表示使用的哈希函数数量
ErrRate表示最大的容错率
bitMap用来指向存储bitmap数据的内存
keys用来存储哈希函数用到的随机数。
这里要详细说明一下,因为根据布隆过滤器的定义,把一个元素加入bitmap的过程中会用到多个哈希函数计算对应的多个哈希值,如果数量足够多,我也不可能编写多种哈希函数去处理(如md5,sha1,sha256等),所以我就采用取巧的方法,使用类似哈希加盐的方式来实现多种哈希函数。通过不同的盐,对同一个输入数据,同一个哈希函数,产生多个不同的哈希值。哈希函数实现如下:
func HMACWithSHA128(seed []byte, key []byte) []byte {
//hmac512 := hmac.New(sha512.New, key)
hmac512 := hmac.New(sha1.New, key)
hmac512.Write(seed)
return hmac512.Sum(nil)
}
接着增加布隆过滤器的三个方法:
// 初始化布隆过滤器
func (f *Filter)Init() {
//分配布隆过滤器位图
f.bitMap = bitset.New(uint(f.BloomSize))
//初始化哈希函数
//是否是类似HMAC-SHA256那种通过改变passphase值形成不同的哈希函数
f.keys = make(map[uint32]bool)
for uint64(len(f.keys)) < f.HashFuncNum {
randNum, err := rand.Int(rand.Reader, new(big.Int).SetUint64(math.MaxUint32))
if err != nil {
panic(err)
}
f.keys[uint32(randNum.Uint64())] = true
}
}
func (f *Filter)Add(elem []byte) {
var buf [4]byte
for k := range f.keys {
binary.LittleEndian.PutUint32(buf[:], k)
hashResult := new(big.Int).SetBytes(HMACWithSHA128(elem ,buf[:]))
result := hashResult.Mod(hashResult, big.NewInt(int64(f.BloomSize)))
//把result对应的bit置1
f.bitMap.Set(uint(result.Uint64()))
}
}
// 判断元素是否在集合里面
func (f *Filter)IsContain(elem []byte) bool {
var buf [4]byte
for k:=range f.keys {
binary.LittleEndian.PutUint32(buf[:], k)
hashResult := new(big.Int).SetBytes(HMACWithSHA128(elem ,buf[:]))
result := hashResult.Mod(hashResult, big.NewInt(int64(f.BloomSize)))
//查询result对应的bit是否被置1
if !f.bitMap.Test(uint(result.Uint64())) {
return false
}
}
return true
}
分别表示初始化布隆过滤器,把元素加入布隆过滤器中,以及判断一个元素是否在布隆过滤器中。
最后增加布隆过滤器的New函数
func NewFilter(elemNum, bloomSize, hashFuncNum uint64, errRate float64) *Filter {
return &Filter{ElemNum:elemNum, BloomSize:bloomSize, HashFuncNum:hashFuncNum, ErrRate:errRate}
}
一个简单的布隆过滤器就做好了,下面写几个测试用例测试一下:
func TestBloomFuncs(t *testing.T) {
var elemNum uint64 = 10000000000
var errRate = 0.0001
var bloomSize = CalBloomSize(elemNum, errRate)
fmt.Println("bloom size in bit:",bloomSize)//1917011675474
fmt.Println("bloom size in Gbyte:",bloomSize/1024/1024/1024/8)
var hashFuncNum = CalHashFuncNum(elemNum, bloomSize)
fmt.Println("hash functions number:",hashFuncNum)
errRate = CalErrRate(elemNum, bloomSize, hashFuncNum)
fmt.Println("error rate:",errRate)
}
func TestBloomFilter(t *testing.T) {
file, err := os.Open("word-list-large.txt")
if err != nil {
t.Error(err)
return
}
defer file.Close()
var wordlist []string
scanner := bufio.NewScanner(file)
for scanner.Scan(){
wordlist = append(wordlist, scanner.Text())
}
var elemNum = uint64(len(wordlist))
var errRate = 0.0001
bloomSize := CalBloomSize(elemNum, errRate)
hashFuncNum := CalHashFuncNum(elemNum,bloomSize)
filter := NewFilter(elemNum, bloomSize, hashFuncNum, errRate)
filter.Init()
for _,elem := range wordlist {
filter.Add([]byte(elem))
}
var testcases = []struct {
Elem string
Want bool
}{
{"hello", true},
{"zoo", false},
{"word", true},
{"alibaba", false},
}
for _,oneCase := range testcases {
got := filter.IsContain([]byte(oneCase.Elem))
if got != oneCase.Want {
if got {
t.Error("should not contain", oneCase.Elem)
} else {
t.Error("contain", oneCase.Elem)
}
t.Error("got != want")
return
}
}
}
完整代码可从github获得:https://github.com/lilianwen/bloomfilter
更多测试用例,后续可能会新增,如从大文件读取巨量测试数据进行测试。
参考资料:
https://mp.weixin.qq.com/s?__biz=Mzg3OTA1MTYzOQ==&mid=2247483700&idx=1&sn=44a9dac2c415b222fb9db5a09e4329a9&chksm=cf0b172cf87c9e3a9213325f5879f794754d601ce4bd7dff71b97da14379b0abafb42978bc9b&scene=27#wechat_redirect
最后
以上就是深情冷风为你收集整理的golang实现简单的布隆过滤器的全部内容,希望文章能够帮你解决golang实现简单的布隆过滤器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复