位图
1.位图的概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景,通常是用来判断某个数据存不存在的。
2.位图的实现
namespace BitSet
{
class bitset
{
public:
bitset(size_t n)
{
_bit.resize(n / 32 + 1, 0);
_num = 0;
}
void set(size_t x)
{
size_t index = x / 32;
size_t pos = x % 32;
_bit[index] |= (1 << pos);
++_num;
}
void reset(size_t x)
{
size_t index = x / 32;
size_t pos = x % 32;
_bit[index] &= ~(1 << pos);
--_num;
}
bool test(size_t x)
{
size_t index = x / 32;
size_t pos = x % 32;
return _bit[index] & (1 << pos);
}
private:
vector<int> _bit;
size_t _num;
};
}
位图的应用
a.快速查找某个数据是否在一个集合中。
b.排序。
c.求两个集合的交集,并集等。
d.操作系统中磁盘块标记。
3.布隆过滤器
3.1布隆过滤器的提出
我们在使用客户端看视频时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉已经看过的内容。服务器会记录用户看过的所有历史记录,当推荐系统推荐时会从每个用户的历史记录中进行筛选,过滤掉哪些已经存在的记录。
a.用哈希表存储用户记录浪费空间。
b.用位图存储用户记录不能处理哈希冲突。
c.将哈希和位图结合起来:布隆过滤器。
3.2布隆过滤器的概念
布隆过滤器是一种比较紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,判断某样东西存不存在,他是用多个哈希函数将一个数据映射到位图结构中,不仅可以提升查询效率。也可以节省大量的内存空间。
3.3布隆过滤器的实现
#pragma once
#include"bitset.h"
namespace BloomFilter
{
struct HashStr1
{
size_t operator()(const string &s)
{
size_t ret = 0;
for (size_t i = 0; i < s.size(); i++)
{
ret *= 131;
ret += s[i];
}
return ret;
}
};
struct HashStr2
{
size_t operator()(const string& s)
{
size_t ret = 0;
size_t magic = 63689;
for (size_t i = 0; i < s.size(); i++)
{
ret *= magic;
ret += s[i];
magic *= 378551;
}
return ret;
}
};
struct HashStr3
{
size_t operator()(const string& s)
{
size_t ret = 0;
for (size_t i = 0; i < s.size(); i++)
{
ret *= 65599;
ret += s[i];
}
return ret;
}
};
template<class K = string, class Hash1 = HashStr1, class Hash2 = HashStr2, class Hash3 = HashStr3>
class bloomfilter
{
public:
bloomfilter(size_t num)
:_bs(5 * num)
, _num(5 * num)
{}
void set(K key)
{
size_t index1 = Hash1()(key) % _num;
size_t index2 = Hash2()(key) % _num;
size_t index3 = Hash3()(key) % _num;
_bs.set(index1);
_bs.set(index2);
_bs.set(index3);
}
bool test(K key)
{
size_t index1 = Hash1()(key) % _num;
if (_bs.test(index1) == false)
return false;
size_t index2 = Hash2()(key) % _num;
if (_bs.test(index2) == false)
return false;
size_t index3 = Hash3()(key) % _num;
if (_bs.test(index3) == false)
return false;
return true;
}
private:
bitset _bs;
size_t _num;
};
}
3.4布隆过滤器优点
a.增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关。
b.哈希函数相互之间没有关系,方便硬件并行运算。
c. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势。
d.在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势。
e.数据量很大时,布隆过滤器可以表示全集,其他数据结构不能。
f.使用同一组散列函数的布隆过滤器可以进行交、并、差运算。
3.5布隆过滤器缺陷
a.有误判率,即不能准确判断元素是否在集合中。
b.不能获取元素本身。
c.一般情况下不能从布隆过滤器中删除元素。
d.如果采用计数方式删除,可能会存在计数回绕问题。
4.海量数据处理
1.给定100亿个整数,设计算法找到只出现一次的整数。
数据的出现次数分为三种:出现0次,出现1次,出现2次及以上。
我们使用两个位表示一个整数,00为0次,01为1次,10为2次及以上。
2.给两个文件,分别有100亿个整数,1G内存如何找到交集。
方案1:将其中一个文件的整数映射到一个位图中,读取另外一个文件中的整数,判断在不在位图,在就是交集,消耗500M内存。
方案2:将文件A的整数映射到位图1,文件B的整数映射到位图2,两个位图按位与,与之后为1的位就是交集,消耗1G内存。
3.一个文件用100亿个int,1G内存,设计算法找到出现次数不超过两次的所有数字。
和第一题思路差不多,还是用两个位表示一个整数,00为0次,01为1次,10为2次,11为3次及以上。
4.给两个文件,分别有100亿个query,只有1G内存,找到两个文件的交集。
平均一个query30-60字节,100亿个query大约占用300-600G。
近似算法:将文件A中的query映射到一个布隆过滤器,读取文件B中的query,判断在不在布隆过滤器中,在就是交集。但是交集中有些数不准确。
精确算法:把两个文件A和B进行哈希切分(i=hashsrt(query)%1000)为多个小文件,i是多少query就进入第Ai/Bi的小文件中。A和B中相同的query一定进入编号相同的Ai和Bi小文件,只需要编号相同小文件找交集就可以。
5.如何扩展布隆过滤器使得它支持删除元素的操作。
每个位标记成计数器。
如果给的位少了,多个值映射到一个位置会导致计数器溢出。比如一个字节最多计数搭配256,如果有257个值都映射到了一个位置,就出问题了。但使用更多的位映射一个位置,空间消耗就大了,布隆过滤器的特点是为了节省空间。
6.给一个超过100G大小的logfile,log中存着IP地址,设计算法找到出现次数最多的IP地址。
首先这里要做的是统计次数,统计次数我们一般用kv模型的map解决。先创建1000个小文件,A0~A999,读取IP计算出i=hashstr(IP)%1000。i是多少,IP就进入对应编号的Ai小文件,这样同一IP地址一定进入同一小文件。map<string, int>count读取Ai中的IP统计出次数,读完一个小文件就clear,再读另一个。使用pair<string, int>max记录出现次数最多的IP就可以求出。
5.一致性哈希
一般情况下,用户的信息存储在服务器(假设有10W个服务器)。如果一个用户要浏览和删除这条信息,一个到那台服务器去查找。这就需要用户和服务器建立一个映射关系(i=hashstr(Mrsw)%10W)。i是多少就存到第i号服务器(实际中可能需要一台额外的服务器存储服务器编号和IP的映射关系,这样子算出i就可以找到他的IP,就可以访问对应服务器了)。
上面的方案有一个很大的缺陷,随着用户数据越来越多,10W台服务器不够用了,我们要将服务器增加到15W台,之前的映射关系就不对了,需要重新计算位置迁移数据。解决这个缺陷就要用一致性哈希。
一致性哈希的映射关系是:i=hashstr(Mrsw)%2^32。用0~ 2^32-1中一段范围的值去映射一台服务器,整个段的范围就映射这10W台服务器。这是如果增加5W台服务器,不需要所以数据迁移,只需要迁移部分负载重的服务器上的数据。
一致性哈希就是给一个特别大的除数,就算增加服务器也不需要重新计算迁移。它是一段范围映射一台机器<x1~x2, IP>,那么增加机器只需要改变映射范围即可,且迁移小部分的数据。