Bloom Filter

149 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 28 天,点击查看活动详情

Bloom Filter

Bloom Filter(布隆过滤器)是一种高效的数据结构,用于快速判断一个元素是否属于一个集合。它通过使用位向量(bit array)和哈希函数(hash function)来实现这一目的,可以快速判断一个元素是否可能存在于集合中,但不能保证正确性。

Bloom Filter的基本原理是:使用多个哈希函数将元素映射到位向量中的多个位上,将这些位设置为1。当需要检查一个元素是否存在于集合中时,将该元素通过哈希函数映射到位向量上,并检查对应的位是否都为1,如果都为1,则该元素可能存在于集合中,否则可以确定该元素不存在于集合中。

Bloom Filter的优点在于,它的空间占用率很小,因为它只使用了一个位向量和多个哈希函数。此外,它的查询速度非常快,只需要进行一次哈希操作和多次位向量访问操作即可。因此,它在处理大规模数据集时非常有用,可以快速判断一个元素是否在集合中,例如在搜索引擎中对已经爬取过的网址进行去重。

然而,Bloom Filter也存在一些缺点。首先,它无法保证正确性,因为哈希函数可能会产生冲突,导致多个元素被映射到相同的位上。其次,随着位向量的增大和哈希函数的增多,误判率会逐渐增大。因此,在使用Bloom Filter时需要根据实际应用场景和误判率要求来选择位向量大小和哈希函数个数。

p=(1eknm)kp = {(1 - {e^{ - \frac{{kn}}{m}}})^k}

  • p :误判率,Bloom Filter 返回该元素可能存在于集合中,但实际上该元素不存在于集合中的概率。
  • k :Hash 函数的个数
  • n :要插入的元素数量
  • m:二进制向量的长度

知道p、n和m时,逆向推导k:

k=mnln11pk = \frac{m}{n}\ln \frac{1}{{1 - p}}

计算最优哈希函数个数的数学公式: 哈希函数个数 k = (m/n) * ln(2)

代码实现

// BloomFilter是布隆过滤器数据结构
type BloomFilter struct {
   bitset []bool     // 位向量
   hashfn []hashFunc // 哈希函数
}

// hashFunc是哈希函数类型
type hashFunc func(string) uint32

// NewBloomFilter创建一个新的布隆过滤器
func NewBloomFilter(n int, p float64) *BloomFilter {
   m := int(-float64(n) * math.Log(p) / (math.Ln2 * math.Ln2))
   k := int(math.Ceil(math.Ln2 * float64(m) / float64(n)))
   bitset := make([]bool, m)
   hashfn := make([]hashFunc, k)
   for i := 0; i < k; i++ {
      hashfn[i] = func(s string) uint32 {
         h := fnv.New32a()
         h.Write([]byte(s))
         return h.Sum32() % uint32(m)
      }
   }
   return &BloomFilter{bitset, hashfn}
}

// Add添加一个元素到布隆过滤器
func (bf *BloomFilter) Add(s string) {
   for _, f := range bf.hashfn {
      idx := f(s)
      bf.bitset[idx] = true
   }
}

// Contains检查元素是否存在于布隆过滤器中
func (bf *BloomFilter) Contains(s string) bool {
   for _, f := range bf.hashfn {
      idx := f(s)
      if !bf.bitset[idx] {
         return false
      }
   }
   return true
}

func main() {
   // 创建一个新的布隆过滤器
   bf := NewBloomFilter(1000000, 0.01)

   // 从标准输入中读取元素并添加到布隆过滤器中
   scanner := bufio.NewScanner(os.Stdin)
   for scanner.Scan() {
      s := scanner.Text()
      bf.Add(s)
   }

   // 从标准输入中读取元素并检查是否存在于布隆过滤器中
   scanner = bufio.NewScanner(os.Stdin)
   for scanner.Scan() {
      s := scanner.Text()
      if bf.Contains(s) {
         fmt.Printf("%s may exist in the set\n", s)
      } else {
         fmt.Printf("%s definitely does not exist in the set\n", s)
      }
   }
}