关于 Bloom Filter 的原理不做介绍,网上各种资料满天飞,其中参考资料 1 已经讲解的很详细。

我重点关注如何用 Rust 实现一个简单的 Bloom Filter,并学习一些语法,源码在参考资料 2 。

BloomFilter 结构体

pub struct BloomFilter<T> {
    hasher: T,
    k: u32,
    bit_vec: BitVec,
    insert_count: u64,
}
  • 尖括号中的 T 代表泛型,这样我们就可以使用不同的 hash 函数实现 (hasher)
  • k 表示使用几个 hash 函数,根据 BF 的原理,使用多个 hash 能减少 False Positive
  • bit vec 表示使用一个多大的 bit 数组,这个关系到 BF 的命中率和 FP 率

BitVec 的作用等于是实现了 bloom filter 的 bit array,直接用这个库省略了作者重复实现一个。

定义 BloomHasher

定义这个 trait 的目的是让所有的 hash 函数库都有 hash() 这个函数,方便在上面的 hasher 中调用。

因为通常,不同的函数库有不同的调用方法名字,定义这个 trait 之后,为每个函数库实现 BloomHasher,这样对于所有的库函数都有了 hash()

pub trait BloomHasher {
    /// Returns the hashed value of the bytes given some seed.
    #[inline]
    fn hash(&self, seed: u32, bytes: &[u8]) -> u32;
}

/// A unit struct for the murmur3 hash function.
pub struct Murmur3;

impl BloomHasher for Murmur3 {
    fn hash(&self, seed: u32, bytes: &[u8]) -> u32 {
        let mut cursor = Cursor::new(bytes);
        murmur3_32(cursor.by_ref(), seed)
    }
}

在这个例子中用的 hash 函数Murmur3::murmur3_32, 从他的名字可以看出,返回结果是一个 u32 类型,也就是说,任意的输入可以获得一个 u32 的输出,并且同样的输入,加上不同的seed,也会得到不同的结果。

比如下面这个例子

extern crate murmur3;

use std::io::Cursor;
use murmur3::murmur3_32;

fn main() {
    println!("seed = 0, result = {}", murmur3_32(&mut Cursor::new("hello world"), 0));
    println!("seed = 1, result = {}", murmur3_32(&mut Cursor::new("hello world"), 1));
}

实现 Bloom Filter

接下来就是正题了,为 BloomFilter 结构体实现方法。

impl<T: BloomHasher> BloomFilter<T> {

pub fn insert(&mut self, bytes: &[u8]) {
        for seed in 0..self.k {
            let hash = self.hasher.hash(seed, bytes) as usize % self.bit_vec.len();
            self.bit_vec.set(hash, true);
        }
        self.insert_count += 1;
    }
}

pub fn contains<B: AsRef<[u8]>>(&self, bytes: B) -> bool {
        for seed in 0..self.k {
            let hash = self.hasher.hash(seed, bytes.as_ref()) as usize % self.bit_vec.len();
            if !self.bit_vec[hash] {
                return false;
            }
        }

        true
    }
}

理解了 bitvec 和 murmur3 库函数的用法后,再看上面的实现就非常简单了。

其中一个语法知识点是 impl 后面声明 T,这样 Rust 就知道 BloomFilter<T> 的尖括号中的类型是泛型而不是具体类型 , 从而在 BloomFilter<T> 上实现的方法中就可以使用它了。

参考资料

  1. https://mp.weixin.qq.com/s/Y7OJ0ntjU0pumWuwFoY8mQ
  2. https://github.com/sagalasan/bloom-filter.git