levelDB布隆过滤器实现分析

levelDB布隆过滤器实现

布隆过滤器是这样一个数据结构,它可以用来判断某些元素是否可能已存在(于下一层的存储介质中);它可能把不存在的元素误认为已存在,但不会把已存在的元素误认为不存在。所以如果布隆过滤器认为一个元素不存在,那么它就真的不存在,如果它认为一个元素已存在,那么它只是可能存在。

原理

布隆过滤器的底层是一个位数组结构。

每次add一个元素,布隆过滤器都将使用k个哈希函数对此元素分别计算得到k个哈希值,哈希值模位数组长度的结果对应位数组中一个位置,然后布隆过滤器将这k个位置都置为1.

每次find一个元素,布隆过滤器还是使用同样的k个哈希函数找到此元素对应的k个位置,如果这k个位置都为1,那么此元素可能存在,否则一定不存在。

*一个数学上的结论是:当哈希函数的个数k = ln2 (位数组长度 / 元素个数)时,布隆过滤器获得最优的准确性。

实现

笔者将util/bloom.cc::BloomFilterPolicy类剥离出来,因此下面的代码能够脱离levelDB运行。

哈希函数:

// includes...
inline uint32_t DecodeFixed32(const char* ptr) {
  const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);

  // Recent clang and gcc optimize this to a single mov / ldr instruction.
  return (static_cast<uint32_t>(buffer[0])) |
         (static_cast<uint32_t>(buffer[1]) << 8) |
         (static_cast<uint32_t>(buffer[2]) << 16) |
         (static_cast<uint32_t>(buffer[3]) << 24);
}

static uint32_t Hash(const char* data, size_t n, uint32_t seed) {
  // Similar to murmur hash
  const uint32_t m = 0xc6a4a793;
  const uint32_t r = 24;
  const char* limit = data + n;
  uint32_t h = seed ^ (n * m);

  // Pick up four bytes at a time
  while (data + 4 <= limit) {
    uint32_t w = DecodeFixed32(data);
    data += 4;
    h += w;
    h *= m;
    h ^= (h >> 16);
  }

  // Pick up remaining bytes
  switch (limit - data) {
    case 3:
      h += static_cast<uint8_t>(data[2]) << 16;
    case 2:
      h += static_cast<uint8_t>(data[1]) << 8;
    case 1:
      h += static_cast<uint8_t>(data[0]);
      h *= m;
      h ^= (h >> r);
      break;
  }
  return h;
}

static uint32_t BloomHash(const string &key)
{
    return Hash(key.data(), key.size(), 0xbc9f1d34);
}

布隆过滤器核心实现:

class BloomFilterPolicy
{
public:
    explicit BloomFilterPolicy(int bits_per_key) : bits_per_key_(bits_per_key)
    {
        // We intentionally round down to reduce probing cost a little bit
        k_ = static_cast<size_t>(bits_per_key * 0.69); // 0.69 =~ ln(2)
        if (k_ < 1)
            k_ = 1;
        if (k_ > 30)
            k_ = 30;
    }

    const char *Name() const { return "leveldb.BuiltinBloomFilter2"; }

    void CreateFilter(const string *keys, int n, std::string *dst) const
    {
        // Compute bloom filter size (in both bits and bytes)
        size_t bits = n * bits_per_key_;

        // For small n, we can see a very high false positive rate.  Fix it
        // by enforcing a minimum bloom filter length.
        if (bits < 64)
            bits = 64;

        size_t bytes = (bits + 7) / 8;
        bits = bytes * 8;

        const size_t init_size = dst->size();
        dst->resize(init_size + bytes, 0);
        dst->push_back(static_cast<char>(k_)); // Remember # of probes in filter
        char *array = &(*dst)[init_size];
        for (int i = 0; i < n; i++)
        {
            // Use double-hashing to generate a sequence of hash values.
            // See analysis in [Kirsch,Mitzenmacher 2006].
            uint32_t h = BloomHash(keys[i]);
            const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
            // 取哈希值 + delta * i模total_size()作为置为1的位置,一共有k_个位置。
            // 这里用delta * i巧妙地将k个哈希值的计算化简了
            for (size_t j = 0; j < k_; j++)
            {
                const uint32_t bitpos = h % bits; // bitpos即此元素对应的位置之一
                array[bitpos / 8] |= (1 << (bitpos % 8));
                h += delta;
            }
        }
    }

    bool KeyMayMatch(const string &key, const string &bloom_filter) const
    {
        const size_t len = bloom_filter.size();
        if (len < 2)
            return false;

        const char *array = bloom_filter.data();
        const size_t bits = (len - 1) * 8;

        // Use the encoded k so that we can read filters generated by
        // bloom filters created using different parameters.
        const size_t k = array[len - 1];
        if (k > 30)
        {
            // Reserved for potentially new encodings for short bloom filters.
            // Consider it a match.
            return true;
        }

        uint32_t h = BloomHash(key);
        const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
        for (size_t j = 0; j < k; j++)
        {
            const uint32_t bitpos = h % bits; // bitpos即此元素对应的位置之一
            if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0)
                return false;
            h += delta;
        }
        return true;
    }

private:
    size_t bits_per_key_; // 位数组长度 ÷ 元素个数
    size_t k_; // 哈希函数的个数,也是实际位数组中每个元素对应的位的个数
};

注意levelDB的布隆过滤器接口和普通的布隆过滤器不同,一般的布隆过滤器是有一个add接口,每次添加一个元素,而在levelDB中,因为布隆过滤器只服务于一个sstable中的keys,而这些keys都是一次性准备好的,所以提供了一个CreateFilter接口,一次性添加n个keys到布隆过滤器中。

另外,CreateFilter接口需要用户自行提供一个std::string作为位数组。

测试代码:

int main() {
    BloomFilterPolicy policy(10);
    cout << policy.Name() << endl;
    
    vector<string> strs{"hello", "world", "fuck", "i", "5432", "helofxx"};

    std::string dst;
    policy.CreateFilter(strs.data(), strs.size(), &dst);

    cout << boolalpha;
    cout << policy.KeyMayMatch("hello", dst) << endl;
    cout << policy.KeyMayMatch("y", dst) << endl;
    cout << policy.KeyMayMatch("234", dst) << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值