经典bloom算法(**布隆过滤器**)-levelDB拆分

bloom算法(布隆过滤器)

原理

先说一下什么是布隆过滤器,Bloom Filter是1970年由布隆提出的,它实际上是一个很长的二进制向量,和一系列随机值映射的函数,主要用于判断一个元素是否在一个集合中。

通常判断一个元素是否在一个集合中,一般是将元素和所有集合中的元素进行对比,当前元素和集合中元素某个元素完全一致的时候,就认为当前元素在该集合中,这时常借助树、散列表、链表以及数组等先存储对应的元素,然后在进行对比。这种情况下时间复杂度为O(logn) , O(1), O(n)

当元素数量比较小时,使用这些数据接口进行比对没有什么问题,但是随着元素个数的增加,对比的时间最低也是线性增长的,为了解决这个问题Bloom Filter就应运而生了。

布隆过滤器由一个固定大小的二进制向量或者位图和一系列映射函数组成,在初始化状态下所有状态都会被置为零。

在这里插入图片描述

创建过滤器的过程中,将集合中的所有值,通过k个映射函数,映射为过滤器中对应的点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C24IJIpu-1669952236125)(https://ny5odfilnr.feishu.cn/space/api/box/stream/download/asynccode/?code=OTU4NWY1ZmExODNlZmExODJjZjk0NmVkODcyMTc4YTFfaFYxckNkREFYRHVDU0dFRTVWSmoxd1RrV1dPMGZJZnJfVG9rZW46Ym94Y25LRWJGcm5qZTNZdHVpM0xyTmNhUExjXzE2Njk5NTIxMDg6MTY2OTk1NTcwOF9WNA)]

当查询某个变量是否存在时,只需要按照同样的操作步骤,然后看对应的对应位置上是否全部为1,来判断集合中是否有这个值。

  • 如果集合中有任意一个k映射出来的值不符合,说明被查询的标量一定不存在集合中

  • 如果k映射的所有值都是存在的,则说明被查询的值有可能在集合中。

误判性

为什么可能存在? 当hash函数生成的散列值发生碰撞时,就有可能发生两个不同的值生成的散列值缺失相同的,还有就是经过多个元素映射的布隆过滤器,某个值的散列值经过k的映射刚好全部为1,但是这些1是多个元素一起映射的结果,而不是由单个元素映射在布隆过滤器上的。

特性

  1. 一个元素被判断存在的时候不一定存在,但是被判断结果为不存在的时候一定不存在

  2. 布隆过滤器可以添加元素,但是不能删除元素,因为删除元素会导致判断误判性增加,除非重新映射一遍

优势

相比于其他数据结构,布隆过滤器在空间和时间方面都有巨大的优势,插入和查询的时间空间复杂度都是常数O(K)

劣势

误算率比较高,随着元素的增加误算率也会随之增加,但是元素太少时,还不如直接使用散列表,因此要把握好元素的度。

不能在布隆过滤器中删除元素

代码实现

接口定义


class LEVELDB_EXPORT FilterPolicy {
 public:
  virtual ~FilterPolicy();

  // Return the name of this policy.  Note that if the filter encoding
  // changes in an incompatible way, the name returned by this method
  // must be changed.  Otherwise, old incompatible filters may be
  // passed to methods of this type.
  virtual const char* Name() const = 0;

  // keys[0,n-1] contains a list of keys (potentially with duplicates)
  // that are ordered according to the user supplied comparator.
  // Append a filter that summarizes keys[0,n-1] to *dst.
  //
  // Warning: do not change the initial contents of *dst.  Instead,
  // append the newly constructed filter to *dst.
  virtual void CreateFilter(const Slice* keys, int n,
                            std::string* dst) const = 0;

  // "filter" contains the data appended by a preceding call to
  // CreateFilter() on this class.  This method must return true if
  // the key was in the list of keys passed to CreateFilter().
  // This method may return true or false if the key was not on the
  // list, but it should aim to return false with a high probability.
  virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0;
};

hash函数,levelDB中使用的hash函数不是stl中标准的Hash函数是自己实现的一个hash函数

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

    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;
                FALLTHROUGH_INTENDED;
            case 2:
                h += static_cast<uint8_t>(data[1]) << 8;
                FALLTHROUGH_INTENDED;
            case 1:
                h += static_cast<uint8_t>(data[0]);
                h *= m;
                h ^= (h >> r);
                break;
        }
        return h;
    }

函数实现


        class BloomFilterPolicy : public FilterPolicy {
        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 = m/n * (ln2)来计算hash函数个数, m bit个数,n插入元素个数
                // leveldb 中简单处理了,一般这里会直接传入 bits_per_key 为 10进行计算
                k_ = static_cast<size_t>(bits_per_key * 0.69);  // 0.69 =~ ln(2)
                // 保证K_是处于  [1,30] 之间
                if (k_ < 1) k_ = 1;
                if (k_ > 30) k_ = 30;
            }

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

            // 将传入的n个 key 存储到bloomfilter 中,bloomfilter结果使用string存储。
            void CreateFilter(const Slice *keys, int n, std::string *dst) const override {
                // Compute bloom filter size (in both bits and bytes)
                // bloomfilter需要多少bit  bits_per_key_ = (m/n) m bit个数,n插入元素个数
                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太小的时候,会导致过滤器一直虚报错误,这里保证bits不小于64就可以了
                if (bits < 64) bits = 64;

                // 这里只是保证bits能够按照8位对齐
                size_t bytes = (bits + 7) / 8;
                bits = bytes * 8;
                // 在string中分配空间
                const size_t init_size = dst->size();
                dst->resize(init_size + bytes, 0);
                // string最后一个元素存储使用的 hash函数的个数
                dst->push_back(static_cast<char>(k_));  // Remember # of probes in filter
                // 获取string 内部的char型数组,数组指向多申请出来的内存,多申请出来的内存,用来存放Bloom hash映射值
                char *array = &(*dst)[init_size];
                for (int i = 0; i < n; i++) {
                    // 通过一次hash计算 一次delta计算,循环K_次将对应的 key在bits上进行置位
                    // Use double-hashing to generate a sequence of hash values.
                    // See analysis in [Kirsch,Mitzenmacher 2006].
                    // leveldb使用一个hash函数,每次对hash值向右移位17bit来模拟实现多个hash函数
                    uint32_t h = BloomHash(keys[i]);
                    const uint32_t delta = (h >> 17) | (h << 15);  // Rotate right 17 bits
                    // 多次重新计算hash模仿多个 hash函数,这里换成多个hash函数也是一样的
                    for (size_t j = 0; j < k_; j++) {
                        // 保证h 的长度不大于bloom过滤器的长度
                        const uint32_t bitpos = h % bits;
                        // 对对应位置进行置位
                        array[bitpos / 8] |= (1 << (bitpos % 8));
                        // 更新获得一个新的hash数值
                        h += delta;
                    }
                }
            }

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

                const char *array = bloom_filter.data();
                // 最后一个byte代表了使用多少hash函数
                // 除最后一个byte之外代表bit数组,详情将CreateFilter函数
                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.
                // 存储的是hash函数的个数
                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;
                    // (array[bitpos / 8] & (1 << (bitpos % 8)))
                    // 查找对应的bit位是否为0 若为0说明肯定不存在,就直接返回
                    if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0) return false;
                    h += delta;
                }
                // 只是说明可能存在
                return true;
            }

        private:
            // 平均每个key拥有的bit数目
            size_t bits_per_key_;
            // hash func的个数
            size_t k_;
        };
<think>好的,我现在需要帮助用户解决关于Dify循环实现的问题。首先,我需要明确用户的具体需求。他们提到“dify 循环实现”或“dify 如何处理循环”,但Dify本身可能没有直接称为“循环”的功能,所以需要结合现有文档中的信息来推断可能的解决方案。 根据用户提供的引用内容,Dify工作流中有迭代节点[^1]。这可能类似于编程中的循环结构,用于重复执行某个任务。例如,在处理列表数据或需要多次执行同一操作时,迭代节点可以派上用场。我需要详细解释迭代节点的配置和使用步骤,确保用户能够理解如何设置循环次数或遍历数据集。 另外,用户可能对如何处理循环中的错误或性能问题感兴趣。引用[2]提到在Dify中处理PEM文件的问题,这可能涉及在循环中正确处理数据格式,避免错误积累。需要提醒用户在循环中注意错误处理和资源管理,确保每次迭代的稳定性。 引用[3]中的例子展示了如何结合FastAPI和Selenium进行实时监控并生成GIF,这可能涉及循环截取浏览器内容。虽然Dify本身可能不直接控制这类循环,但可以通过外部脚本或定时任务触发工作流,模拟循环执行。这部分需要说明如何将外部循环机制与Dify结合使用。 接下来,我需要组织回答结构。首先介绍迭代节点的基本概念和配置步骤,然后讨论循环中的数据处理和错误处理,最后补充外部触发循环的方法。同时,生成相关问题以帮助用户进一步探索,比如优化循环性能或处理复杂数据结构。 需要确保回答符合格式要求,使用正确的中文表达,行内公式用$...$,独立公式用$$...$$,但当前问题不涉及数学公式,所以可能不需要使用。同时,引用标识要自然地添加到相关段落末尾,如、[^2]、[^3]。 最后,检查是否有遗漏点,比如Dify的其他循环相关功能或用户可能遇到的常见问题,确保回答全面且易于理解。</think>在Dify中处理循环逻辑主要通过**迭代节点**实现,以下是具体实现方式和应用场景的解析: ### 一、Dify循环实现机制 Dify通过**工作流设计器**中的迭代节点处理循环需求,其核心原理类似编程中的`for循环`。迭代节点可遍历以下数据类型: - 数组列表:`["A","B","C"]` - 字典集合:`{"key1":"value1", "key2":"value2"}` - 数值范围:通过`range()`函数生成序列 配置示例: ```python # 模拟迭代节点的数据输入 input_data = { "dataset": [1,2,3,4,5], "process_logic": "item * 2" # 对每个元素执行乘以2的操作 } ``` ### 二、迭代节点的关键配置步骤 1. **数据源绑定**:将数组/字典类型变量连接到迭代节点的输入端口 2. **循环变量命名**:设定当前元素的变量名(默认为`item`) 3. **子流程设计**:在迭代节点内部构建需要重复执行的逻辑模块 4. **结果聚合**:通过`outputs`收集所有迭代结果,支持数组或对象格式 $$ \text{总耗时} = \sum_{i=1}^{n}(单次迭代时间_i) + 系统开销 $$ ### 三、循环中的特殊处理 1. **错误中断控制**: - 启用`continueOnError`参数可跳过失败迭代 - 通过`try-catch`模块包裹敏感操作 2. **并行优化**: ```python # 伪代码示例 Parallel.forEach(dataset, lambda item: process(item)) ``` 3. **结果过滤**: ```python filtered = filter(lambda x: x%2==0, processed_results) ``` ### 四、应用场景案例 1. **批量文件处理**:遍历存储桶中的文件列表进行格式转换 2. **数据清洗**:对数据库查询结果集进行逐条校验 3. **API轮询**:定时循环调用第三方接口直到满足特定条件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Achilles.Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值