LevelDB:Bloom源码精读——数据结构

简介: 一、原理分析 BloomFiler(布隆过滤器)是由Howard Bloom在1970年提出的二进制向量数据结构,怎么来理解“二进制向量数据结构”? 我们将其分解成“二进制”、“向量”和“数据结构”来分别理解。 1、二进制:用0和1来表示的数。 2、向量:是指位向量或者比特向量,即向量的坐标

一、原理分析

BloomFiler(布隆过滤器)是由Howard Bloom在1970年提出的二进制向量数据结构,怎么来理解“二进制向量数据结构”?

我们将其分解成“二进制”、“向量”和“数据结构”来分别理解。

1、二进制:用0和1来表示的数。

2、向量:是指位向量或者比特向量,即向量的坐标系的X轴是位列(连续的内存地址),Y轴是0和1两个值。

3、数据结构:存储和组织数据的方式。

我们可以这样形象理解BloomFiler,它是一段位列,位列上每一位以0或1表示着BloomFiler组织数据的意义。

而BloomFiler组织数据是将数据通过K个哈希函数分别映射到位列上,并将位列相应位置的位值赋值为1。位值为1的意义是表示数据在BloomFiler中存在。

如图:

1、位列,开始没有数据

      

2、将数据a哈希函数分别映射到位列上,并将位列相应位置的位赋值为1。

      

3、将数据b哈希函数分别映射到位列上,并将位列相应位置的位赋值为1。

      

查询一个数据是否存在于BloomFiler中,即将数据通过K个哈希函数分别映射到位列上,看位列相应的位置上的位值是否都为1,

如果都为1,则说明存在;如果不都为1,则说明不存在。

由于哈希存在冲突,存在的情况下,有一定的误识别率。即一个数本来不存在于BloomFiler中,而被告诉存在。


二、代码实现


        static uint32_t BloomHash(const Slice& key) // 哈希函数
        {
            return Hash(key.data(), key.size(), 0xbc9f1d34);
        }
        
        class BloomFilterPolicy : public FilterPolicy
        {
        private:
            size_t bits_per_key_; // 一个key占多少位
            size_t k_; // 哈希函数个数
            
        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;
            }
            
            virtual const char* Name() const
            {
                return "leveldb.BuiltinBloomFilter2";
            }
            
            // n:key的个数;dst:存放过滤器处理的结果
            virtual void CreateFilter(const Slice* 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.
                // 位列bits最小64位,8个字节
                if (bits < 64) bits = 64;
                
                // bits位占多少个字节
                size_t bytes = (bits + 7) / 8;
                // 得到真实的位列bits
                bits = bytes * 8;
                
                const size_t init_size = dst->size();
                dst->resize(init_size + bytes, 0);
                // 在过滤器集合最后记录需要k_次哈希
                dst->push_back(static_cast<char>(k_));  // Remember # of probes in filter
                char* array = &(*dst)[init_size];
                for (size_t 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
                    // 使用k个哈希函数,计算出k位,每位都赋值为1。
                    // 为了减少哈希冲突,减少误判。
                    for (size_t j = 0; j < k_; j++)
                    {
                        // 得到元素在位列bits中的位置
                        const uint32_t bitpos = h % bits;
                        /*
                         bitpos/8计算元素在第几个字节;
                         (1 << (bitpos % 8))计算元素在字节的第几位;
                         例如:
                         bitpos的值为3, 则元素在第一个字节的第三位上,那么这位上应该赋值为1。
                         bitpos的值为11,则元素在第二个字节的第三位上,那么这位上应该赋值为1。
                         为什么要用|=运算,因为字节位上的值可能为1,那么新值赋值,还需要保留原来的值。
                         */
                        array[bitpos/8] |= (1 << (bitpos % 8));
                        h += delta;
                    }
                }
            }
            
            virtual bool KeyMayMatch(const Slice& key, const Slice& 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)
                {
                    // 为短bloom filter保留,当前认为直接match 
                    // 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;
                    // 只要有一位为0,说明元素肯定不在过滤器集合内。
                    if ((array[bitpos/8] & (1 << (bitpos % 8))) == 0) return false;
                    h += delta;
                }
                return true;
            }
        };





目录
相关文章
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1022 9
|
5月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
300 3
|
8月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
691 19
|
8月前
|
数据库 C++
【数据结构进阶】红黑树超详解 + 实现(附源码)
本文深入探讨了红黑树的实现原理与特性。红黑树是一种自平衡二叉搜索树,通过节点着色(红/黑)和特定规则,确保树的高度接近平衡,从而实现高效的插入、删除和查找操作。相比AVL树,红黑树允许一定程度的不平衡,减少了旋转调整次数,提升了动态操作性能。文章详细解析了红黑树的性质、插入时的平衡调整(变色与旋转)、查找逻辑以及合法性检查,并提供了完整的C++代码实现。红黑树在操作系统和数据库中广泛应用,其设计兼顾效率与复杂性的平衡。
1619 3
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
590 16
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
951 8
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
1155 4
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
359 3
|
Java C++ 索引
让星星⭐月亮告诉你,LinkedList和ArrayList底层数据结构及方法源码说明
`LinkedList` 和 `ArrayList` 是 Java 中两种常见的列表实现。`LinkedList` 基于双向链表,适合频繁的插入和删除操作,但按索引访问元素效率较低。`ArrayList` 基于动态数组,支持快速随机访问,但在中间位置插入或删除元素时性能较差。两者均实现了 `List` 接口,`LinkedList` 还额外实现了 `Deque` 接口,提供了更多队列操作。
255 3
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
406 0

热门文章

最新文章