• 哈希的应用 位图+布隆过滤器+海量数据处理


    位图

    1.什么是位图

    先看一道面试题:
    给60亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这60亿个数中
    1.如果用set/unordered_set 插入然后调用函数count的话内存不够,60亿个整数大概需要24g的内存
    2.如果用外排序+二分查找也不可以,二分查找需要所有的整数都是有序的,并且要是顺序存储结构支持随机访问
    此时如果用哈希的直接定值法——用一个比特位表示标识映射值在不在,此时需要的内存大概为750M,这个对于内存来说也不是不可以解决,这样的解决方法就叫做位图
    位图: 就是用每一位来存放某种状态,适用于海量数据,且数据无重复的场景。通常是用来判断某个数据是否存在的。

    2.实现思路

    在这里插入图片描述

    3.位图的实现

    注意:位图的实现尽量不要移动需要映射的值X,因为涉及到补位的问题,此外映射值改为X要好一点。

    template <size_t N>
    	class Bitset
    	{
    	public:
    		Bitset()
    		{
    			_bits.resize(N / 8 + 1);//保证足够多的比特位,最多浪费8个
    
    		}
    		//X映射的比特位标记成1
    		void set(size_t x)
    		{
    			//x映射的比特位在第几个char对象
    			size_t i = x / 8;
    			//x在char的第几个比特位
    			size_t j = x % 8;
    			_bits[i] |= (1 << j);
    		}
    		void reset(size_t x)
    		{
    			//x映射的比特位在第几个char对象
    			size_t i = x / 8;
    			size_t j = x % 8;
    			_bits[i] &= (~(1 << j));//次数不能用异或,否则如果原本不存在x异或之后反而存在了
    		}
    	   bool  test(size_t x)
    		{
    			size_t i = x / 8;
    			size_t j = x % 8;
    			return _bits[i] & (1 << j);//此处有一个隐式类型转换,非0就是真,0就是假
    		}
    	private:
    		vector<char>_bits;
    
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    4.位图的应用

    1.给定100亿个整数,设计算法找到只出现一次的整数?
    在这里插入图片描述

    template<class N>
    		class two_bitset
    		{
    		public:
    			void set(size_t x)
    			{
    				int exit1 = _bst1.test(x);
    				int exit2 = _bst2.test(x);
    				if (exit1 == 0 && exit2 == 0)
    				{
    					_bst2.set(x);
    				}
    				else
    				{
    					_bst1.set(x);
    					_bst2.reset(x);
    				}
    			}
    			bool is_once(size_t x)
    			{
    				return (!_bst1.test(x)) && _bst2.test(x);
    
    			}
    		private:
    			Bitset<N>_bst1;
    			Bitset<N>_bst2;
    		};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
    与上述类似,也是用两个比特位表示映射的状态
    此时
    00 出现0次
    01 出现1次
    10 出现两次
    11 出现3次及以上,取中间两次即可
    3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
    思路:
    每个文件都有100亿个整数,此时文件中一定有相同的数,因为整数的最大值才是42亿,此时我们的措施是将每个文件都放到一个位图中,每个文件最多42亿刚好500M,两个文件就是1g,因为放进位图中还起到了去重的作用,最后挨个找交集就可以了,如果此时位图1和位图2对应的比特位都是1的话,那么就是交集。

    布隆过滤器

    可以参考这个链接一个大牛写的布隆过滤器

    1.布隆过滤器思路

    用哈希表存储用户记录,缺点:浪费空间
    上述的位图只能处理整形,但是如果内容编号是字符串,就无法处理了,此时将位图和哈希结合就是布隆过滤器。
    首先将字符串用仿函数转变成整形,但是因为字符串中的字符个数是有限的,必定会存在两个不同的字符串用同一个仿函数映射成相同的整数,所以我们需要用多个不同的哈希函数生成多个哈希值,并对每个生成的bit位置
    eg:现在有一个字符串"baidu"和三个哈希函数,分别生成了哈希值1 4 7
    在这里插入图片描述
    又有一个字符串“tencent”此时其哈希函数映射后对应3 4 8的值如下
    在这里插入图片描述
    可以看到上述有4号位两个字符串的哈希都返回这个bit位,结果可以看到,如果我们查找一个字符串时,如果该字符串对应的bit位为6,说明这个字符串不可能存在,但是如果我们查找一个字符串,此时其对应的bit位都是1,但是该字符串也不一定是存在的,因为有可能多重覆盖,现在有很多的字符串,一个字符串覆盖一个位置,导致本来这个字符串不存在,但事实就是该字符串映射的三个bit位都是1。
    结论:
    布隆过滤器在查找一个字符串时,若该字符串存在,也不能说明一定存在,但是如果该字符串不存在,那么一定是不存在的。
    下面这张图是网上一些人的经验数据
    在这里插入图片描述
    可以看出,哈希函数个数与布隆过滤器的长度与错误率成反比,及哈希函数个数越多,布隆过滤器越长,误差率越低。
    布隆过滤器支持删除吗?
    传统的布隆过率器是不支持删除的,为什么呢?
    删除的话是添加比特位计数
    一个比特位标识一个位置
    八个比特位:0–255
    十六个比特位:0–65535

    在这里插入图片描述可以看出,上述即使删除了,如果某个bit位上有多个值映射对应,还是会有可能出现误差,并且用布隆过滤器增加了空间的消耗,而布隆过滤器的本质就是为了节省空间,支持删除的话就不那么节省了,如果给每个bit位都增加计数的话,还不如增加布隆过滤器的长度,所以说不支持删除,用于那些允许有些许误判的场景。

    2.布隆过滤器的实现

    一个元素用多个哈希函数映射到一个位图,被映射到的那个bit位为1,然后多个哈希函数映射多个位图,查找的时候只要对应一个位图bit为0,那么说明该元素一定不在哈希表中,否则可能在哈希表中。

    //布隆过滤器的实现
    	struct BKDRHash
    	{
    		size_t operator()(const string& s)
    		{
    			// BKDR
    			size_t value = 0;
    			for (auto ch : s)
    			{
    				value *= 31;
    				value += ch;
    			}
    			return value;
    		}
    	};
    
    	struct APHash
    	{
    		size_t operator()(const 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 string& s)
    		{
    			size_t hash = 5381;
    			for (auto ch : s)
    			{
    				hash += (hash << 5) + ch;
    			}
    			return hash;
    		}
    	};
    
    
    	struct JSHash
    	{
    		size_t operator()(const string& s)
    		{
    			size_t hash = 1315423911;
    			for (auto ch : s)
    			{
    				hash ^= ((hash << 5) + ch + (hash >> 2));
    			}
    			return hash;
    		}
    	};
    	template<size_t M,class K = string,//M位布隆过滤器的长度
    		class HashFunc1 = BKDRHash,
    		class HashFunc2 = APHash,
    		class HashFunc3 = DJBHash,
    		class HashFunc4 = JSHash>
    	class BloomFilter
    	{
    	public:
    		void set(const K& key)
    		{
    			size_t hash1 = HashFunc1()(key) % M;
    			size_t hash2 = HashFunc2()(key) % M;
    			size_t hash3 = HashFunc3()(key) % M;
    			size_t hash4 = HashFunc4()(key) % M;
    			_bs.set(hash1);
    			_bs.set(hash2);
    			_bs.set(hash3);
    			_bs.set(hash4);
    
    		}
    		bool Test(const K& key)
    		{
    			size_t hash1 = HashFunc1()(key) % M;
    			if (_bs.test(hash1)==false)
    			{
    				return false;
    			}
    			size_t hash2 = HashFunc2()(key) % M;
    			if (_bs.test(hash2) == false)
    			{
    				return false;
    
    			}
    			size_t hash3 = HashFunc3()(key) % M;
    			if (_bs.test(hash3) == false)
    			{
    				return false;
    			}
    			size_t hash4 = HashFunc4()(key) % M;
    			if (_bs.test(hash4) == false)
    			{
    				return false;
    			}
    			return true;//存在误判
    		}
    
    
    	private:
    		Bitset<M>_bs;
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    3.布隆过滤器小结与应用

    优点:
    1.增加和查询元素的时间复杂度位O(K),(K为哈希函数的个数,一般比较小),与数据量大小无关
    2.哈希函数之间没有关系,方便函数计算
    3.布隆过滤器不需要存储元素本身,对某些保密要求比较严格的场合有很大的优势。
    4.在能够承受一定的误判时,其数据结构有很大的空间优势
    5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
    6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
    缺点:

    1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
    2. 不能获取元素本身
    3. 一般情况下不能从布隆过滤器中删除元素
      应用
      eg:1.注册用户名时
      如果该用户名不存在,则可以直接使用,
      如果存在,再去数据库确认一遍
      2.检查是否为黑名单是
      如果不是,直接放行,
      如果是,再去系统数据库确认一遍。

    海量数据处理(哈希切分)

    1、给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?如何快速找到top K的IP?

    在这里插入图片描述

    1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出
      精确算法和近似算法
      在这里插入图片描述
  • 相关阅读:
    谈谈我对云原生与软件供应链安全的思考
    MongoDB3.x创建用户与用户角色
    docker容器访问宿主机mysql数据库
    注册域名,购买阿里云服务器,备案,域名解析图文教程简介
    pg limit 的使用疑问 --chatGPT
    git原理和命令以及工具
    CentOS8.0搭建 RockerMq
    mysql远程登录
    OpenSSH升级版本(7.4升级到8.9)
    蓝桥杯官网练习题(算式900)
  • 原文地址:https://blog.csdn.net/cxy_zjt/article/details/127824396