赞
踩
目录
前面我们学过为了实现哈希映射,我们需要一个哈希函数,这里我们也可以使用哈希函数把IP转为整型。比方说我们分成了100份小文件,idx = HashFunc(IP) % 100,idx是几就把它放进几号文件中。
我们可以把每个小文件理解为一个哈希桶。
这样不一样的IP可能分进同一个小文件中,但是同一个IP一定会分进同一个小文件。
这里还可能出现一个情况:其中一个小文件的大小可能超过1G(假设超过1G就不够了)。
而超过了1G也有有两种情况:
- 1️⃣ 不重复的IP很多,map需要很多节点,统计不下。
- 2️⃣ 重复的IP很多,map不需要很多节点,统计的下。
针对第一种情况,我们可以换个哈希函数递归切分。
但是这种方法对情况二无效,因为相同的IP太多,照样会切分超过1G。
所以综合考虑可以这样统计:
- 不管是啥情况,都直接用map统计,如果是第二种情况就直接统计完成了。
- 如果是第一种情况,会insert失败,我们可以捕获异常,此时再去换个哈希函数递归切分。
通过上一篇的讲解我们可以看出:①位图的优点是节省空间和效率高
②缺点是要求范围相对集中,而且只能是整型。
而如果是字符串我们想使用位图,就可以使用哈希函数转成整型。
这里就会有一种情况,不同的字符串可能转换成同一个整型。 会导致误判。
- 存在是不准确的,如果只有str1和str2,而str3映射的位置跟str2重了,
- 就会导致原本不在的元素误判成在。
- 所以布隆过滤器会有两种判断状况
- ①不存在(百分百准确)
- ②可能存在(可能误判,即哈希冲突)
它的主要思想是让一个值映射多个位置。我们可以使用多个哈希函数,多映射几个位置,这里假设有两个哈希函数,映射两个位置。
这样我们要看str2是否存在,必须要同时指向红色和绿色才能判断为存在。
所以布隆过滤器的作用就是降低误判率。映射的位置越多,误判率越低。
但是这里映射的位置也不能太多,映射的多,占的空间也多,找的次数也多,我们使用位图这样的方式就是为了提高效率并且节省空间。映射的多了也就没那么节省空间了。
【场景一】
当我们要写一个注册系统的时候,我们注册昵称的时候不能跟别人重复,此时我们就可以采用布隆过滤器,如果不在那么就是准确的,一定不存在。但是如果显示存在,则有可能是误判。因为布隆过滤器中如果存在可能会误判,可以到数据库中再次查询昵称号码存不存在。
有人可能问这有必要加一个布隆过滤器吗?
- 假设现在来了100不存在的值,大部分都会显示不存在,
- 只有很小一部分会误判为存在,这样没有误判的大部分效率大大提高。
【场景二】
我们在访问网站的时候有时候会出现风险网站。我们可以把这些网页加入黑名单,在我们访问网站之前就先经过布隆过滤器,有风险就可以快速的判断。
布隆过滤器最常见的是string类型。 这里要给一个非类型模板参数N以确定开的空间有多大,这里我们写三个哈希函数。而字符串转整型的哈希函数有很多:
各种字符串Hash函数
这里我们就取里面效率较高的三个:
- struct BKDRHash
- {
- size_t operator()(const std::string& s)
- {
- size_t value = 0;
- for (auto ch : s)
- {
- value *= 31;
- value += ch;
- }
- return value;
- }
- };
-
- struct APHash
- {
- size_t operator()(const std::string& s)
- {
- size_t hash = 0;
- for (long i = 0; i < s.size(); i++)
- {
- if ((i & 1) == 0)
- {
- hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
- }
- else
- {
- hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
- }
- }
- return hash;
- }
- };
-
- struct DJBHash
- {
- size_t operator()(const std::string& s)
- {
- size_t hash = 5381;
- for (auto ch : s)
- {
- hash += (hash << 5) + ch;
- }
- return hash;
- }
- };
关于长度的问题这里有专门的文章进行讲述:
详解布隆过滤器的原理
里面有一个公式:
这里n我们是知道的,假设k是3,ln2约等于0.7,最后得到m=4.2*n,所以布隆过滤器多一个数据要开大约4.2个比特位,我们直接按加入一个数据开5个比特位算。
- template<size_t N,
- class K = std::string,
- class HashFunc1 = BKDRHash,
- class HashFunc2 = APHash,
- class HashFunc3 = DJBHash>
- class BloomFilter
- {
- public:
- private:
- std::bitset<N * 5> _bs;
- };
大致思路跟我们上面的位图一样,这里我们使用库里的函数bitset
头文件:#include <bitset>
而set函数库里面已经帮我们实现好了:
- void set(const K& x)
- {
- size_t idx1 = HashFunc1()(x) % (5 * N);
- size_t idx2 = HashFunc2()(x) % (5 * N);
- size_t idx3 = HashFunc3()(x) % (5 * N);
- _bs.set(idx1);
- _bs.set(idx2);
- _bs.set(idx3);
- }
这里只要有一处不在那么就返回false,全部都在才能返回true。
- bool test(const K& x)
- {
- size_t idx1 = HashFunc1()(x) % (5 * N);
- if (!_bs.test(idx1))
- {
- return false;
- }
- size_t idx2 = HashFunc2()(x) % (5 * N);
- if (!_bs.test(idx2))
- {
- return false;
- }
- size_t idx3 = HashFunc3()(x) % (5 * N);
- if (!_bs.test(idx3))
- {
- return false;
- }
- return true;
- }
布隆过滤器一般不能支持删除,因为一个位置可能被多个值映射,删除以后可能把别人的也删掉了。
那么我们能不能强制支持删除呢?
我们可以去计数,有几个值映射计数器就是几,删除了就让当前位置的计数器--
。
但是使用计数又会有问题:因为不知道计数器的范围,所以不能开的太小的比特位,导致使用过多内存。
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和 近似算法。(query就是sql语句,可以理解为一个字符串。,也可能是网络请求url,也就是网址)
- 近似算法我们直接使用布隆过滤器,将一个文件的query语句放进布隆过滤器里,
- 然后另一个文件查找在不在就是交集。虽然有误判:不存在的也被当做交集。
- 但是作为近似算法还是可行的。
- 而精确算法就得用到前面的哈希切割。
- 同时把两个文件都切分成数个小文件,在编号相同的小文件查看交集即可,最后注意去重。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。