如何判斷一個元素在億級數據中是否存在?布隆過濾器

常規實現

1、數組
2、鏈表
3、樹、平衡二叉樹
4、Map(紅黑樹)
5、哈希表

上面的使用方式在結合常見的排序方式比如二分,可以快速的查找數據是否存在,但當集合數據元素非常大,比如1億條,這個時候,數據結構問題就會凸顯出來,數組,鏈表等 ,就會非常喫內存,內存的消耗會成指數級增長,最終達到瓶頸。對於哈希表,其實也是很佔用內存,哈希表的常規做法是key值映射成一個8個字節的信息指紋,對於數據量過大,問題也是依舊存在,只是相對其他方式能夠從某種層面說節省空間

哈希函數

哈希函數的概念是:將任意大小的數據轉換成特定大小的數據的函數,轉換後的數據稱爲哈希值或哈希編碼。下面是一幅示意圖:
在這裏插入圖片描述
可以明顯的看到,原始數據經過哈希函數的映射後稱爲了一個個的哈希編碼,數據得到壓縮。哈希函數是實現哈希表和布隆過濾器的基礎。

布隆過濾器介紹

  1. 巴頓.布隆於一九七零年提出
  2. 一個很長的二進制向量 (位數組)
  3. 一系列隨機函數 (哈希) 空間效率和查詢效率高
  4. 有一定的誤判率(哈希表是精確匹配)

布隆過濾器原理

布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組和幾個哈希函數。假設位數組的長度爲m,哈希函數的個數爲k

在這裏插入圖片描述
以上圖爲例,具體的操作流程:

假設集合裏面有3個元素{x, y, z},哈希函數的個數爲3。首先將位數組進行初始化,將裏面每個位都設置位0。對於集合裏面的每一個元素,將元素依次通過3個哈希函數進行映射,每次映射都會產生一個哈希值,這個值對應位數組上面的一個點,然後將位數組對應的位置標記爲1。查詢W元素是否存在集合中的時候,同樣的方法將W通過哈希映射到位數組上的3個點。如果3個點的其中有一個點不爲1,則可以判斷該元素一定不存在集合中。反之,如果3個點都爲1,則該元素可能存在集合中。注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。可以從圖中可以看到:假設某個元素通過映射對應下標爲4,5,6這3個點。雖然這3個點都爲1,但是很明顯這3個點是不同元素經過哈希得到的位置,因此這種情況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的原因。

布隆過濾器添加元素

將要添加的元素給k個哈希函數
得到對應於位數組上的k個位置
將這k個位置設爲1

布隆過濾器查詢元素

將要查詢的元素給k個哈希函數
得到對應於位數組上的k個位置
如果k個位置有一個爲0,則肯定不在集合中
如果k個位置全部爲1,則可能在集合中

golang版本的代碼

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???")
   }
}
//----------------------------------------------------------------------------
// @ 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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章