对于海量整形数据的处理,通常是上百个G的代码。
通常有如下的应用:
1. 快速查找某个数据是否在一个集合中2. 排序 + 去重3. 求两个集合的交集、并集等4. 操作系统中磁盘块标记
如果将数据加载到内存中,运用基本数据结构处理,那就需要百G的内存,这是非常庞大的。
例如:要在40亿的无符号整型数据中,判断某个数在不在。
判断在否只需要0或1标记,利用hash直接定址法或hash映射,对每一个整形开辟一个char的空间。简单计算空间利用: 无符号整形的范围是2^32 约有42亿9千万 也就是开辟42亿9千万字节/大约要4G的内存空间......
优化:一个字节有8个bit位,每一个bit位记录0/1标记在否 也就是需要42亿9千万的bit位 计算得需要大约500M的内存
首先要对数据处理的范围0-N开空间。bitset需要开N/32 + 1的整形来容纳这些数据。(1个整形=32bit)。这里我们用vector容器作为底层。
位图的成员函数有set(将某位置为1) reset(将某位置为0) test(判断是否存在 1为存在,0为不存在) flip(翻转指定位)
提前开 N/32 + 1的整形空间,并且初始化为全0.
1.计算在第i个int的类型
2.计算在该int类型上j的bit位上
3.利用或运算 ,将第j位置为1
与set相反,将某一位置为0
1.计算i _bits上
2.计算_bits的jbit位
3.与运算 与上 第j位的0,其它位的全1 ~(1<
得到某一位的标记 移位操作和与操作
翻转指定位
1.计算i _bits上
2.计算_bits的jbit位
3._bits[i]异或上 1的左移j位
优点:
判断海量数据的存在,判断出现次数有极大的优势。
节省空间,速度快。本质就是hash映射
缺点:
只能映射整形,对于字符串,浮点型不能存储
海量处理数据,哈希表的存储占用太多空间。
位图不适用于字符串类型的处理
所以将hash和位图相结合。布隆提出过滤器
将字符串通过hash函数映射到位图上。这时会产生hash冲突。对于位图上,判断字符串的结果可以是在于不在。
判断不在是准确的,因为hash映射上标志为0,就代表没有发生hash冲突,也没有这个字符串。
判断在是不准确的。会发生hash冲突。多个字符串有可能映射到这个位上。
为了降低发生hash冲突的概率 布隆过滤器降低冲突概率
一个值映射到一个位置,容易误判(将在不在判断为在)。将一个值映射到多个位置,多个位的结果判断一个值,这样的误判的概率会大大降低。
结论
布隆过滤器是无法彻底解决误判问题。一个key值 通过hash函数映射到多个位,只能降低误判的概率,无法去除误判。
过小的过滤器所有的bit位均为1,查询任何结构都是“可能存在”,起不到过滤的结果。过滤器的长度直接影响误判的结果,越长的过滤器,发生哈希冲突的概率越小,误判几率越小。
hash函数的个数与key映射到位图上的速度有关,如果个数过多,位图上变为1的速度也越快,很快就发生hash碰撞。如果个数过少,误判的几率会变高。
该图展示hash个数和过滤器长度与误判率的关系
由大佬推导出来的公式
当我们设置hash函数为3个时候
k=3,ln2=0.7 m=n*k*0.7 =4.3n;
所以在hash函数为3时,布隆过滤器的长度大致为插入元素个数的4倍。
布隆过滤器要实现成为一个模板类,因为布隆过滤器插入的元素是不固定的(整形,字符串)。而位图要求是整形的存储。所以通过hash函数将字符串转整形。一般情况下,布隆过滤器用于处理字符串,故在模板参数的处理,默认给string的处理。
假定传入三个hash函数,那么布隆过滤器的长度为插入元素的4倍。
hash函数传入三个稳定的默认参数。
hash函数的作用就是把字符串数据转化为整形,便于建立映射关系。如果一个字符串能转化出不同的整形值,那么就有不同的映射位置。所以这里通过BKDRHash\APHash\DJBHash
选用hash冲突概率最优的字符串转化hash函数
字符串转hash文章:
字符串转hash
struct BKDRHash { size_t operator()(const string& key) { // BKDR size_t hash = 0; for (auto e : key) { hash *= 31; hash += e; } return hash; } }; struct APHash { size_t operator()(const string& key) { size_t hash = 0; for (size_t i = 0; i < key.size(); i++) { char ch = key[i]; if ((i & 1) == 0) { hash ^= ((hash << 7) ^ ch ^ (hash >> 3)); } else { hash ^= (~((hash << 11) ^ ch ^ (hash >> 5))); } } return hash; } }; struct DJBHash { size_t operator()(const string& key) { size_t hash = 5381; for (auto ch : key) { hash += (hash << 5) + ch; } return hash; } };
插入字符串“猪八戒”,如果hash函数映射出的三个位置分别为 1 4 7,那么这几个位置为1
此时再存入“tencent”,位图就继续发生改变
判断某一位在与否;
判断在——必须三个hash函数对应的位置同时为1 不是一定正确
判断不在——有一个hash函数的映射位为0 一定正确
由于布隆过滤器存在冲突,在删除一个元素时,同时也有可能影响其它元素。
是不支持删除的
将布隆过滤器中的每位bit扩展成一个小的计数器,插入元素时,k个hash函数映射出的值都+1。删除时,给K个映射位都减一。
缺陷:
存在计数回绕
无法真正确定元素是否真在
可以误判的场景
比如
用户注册名称,需要判断用户的名称是否被用过
这时候如果一个数据不在 ,那么就是准确的。那如果一个名称通过布隆过滤器被判断出在,(实际上不存在),这时候只要通过数据库的再查找。相比于全部再数据库查找,布隆过滤器极大提高效率。
优点:
1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小
2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
缺点
1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
2. 不能获取元素本身
3. 一般情况下不能从布隆过滤器中删除元素
4. 如果采用计数方式删除,可能会存在计数回绕问题
gitee:位图和布隆过滤器的模拟实现
参考资料:
详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)
各种字符串Hash函数 - clq - 博客园 (cnblogs.com)