该文章会长期收录一些关于 海量数据处理 的常见问题,在面试中很容易被问到,希望做以记录帮助到读者。
1. 给定 100亿 个整数,设计算法找到只出现一次的整数?
int
范围的所有数据,即 4G
个数据,我们仅需要 4G / 8 = 512M
大小的空间就够了。512M
的位图,那么就是额外 1G
的空间512M
的位图表现了 int
型数据的所有情况,但只能表现出现过,并不能表现多次出现。究其原因还是只用了 1 个比特位来表示,那么就只能产生 0 、1 这两种情况,在此我们采用 2 个比特位表示一个数字的出现情况,这样就能得到 4 中情况,出现0、1、2、3次,也就是两张普通位图的合并结果,这样做也是 1G
的空间。参照我们普通位图的实现 [C++ 系列] 84. 位图的概念及应用 稍加改造就能解决该问题。参见代码如下:
#include
#include
#include
typedef struct TwoBitSet
{
// 数组
size_t *bts;
// 数据的范围
size_t range;
}TwoBitSet;
void TBSInit(TwoBitSet *tbs,size_t range)
{
assert(tbs);
// 需要两位表示一个数据,所以除以16,
// (range >> 4) + 1计算需要多少个整型
tbs->bts = (size_t *)malloc(((range >> 4) + 1) * sizeof(size_t));
assert(tbs->bts);
memset(tbs->bts, 0,((range >> 4) + 1) * sizeof(size_t));
tbs->range = range;
}
int TBSGetValue(TwoBitSet *tbs, size_t x)//
{
assert(tbs);
// 计算x在数组里下标
size_t index = x >> 4;
// 因为两位表示一位,需要乘2
size_t num = x % 16 * 2;
// 不能改变tbs->bts[index],所以设置value
int value = tbs->bts[index];
// 将value向右移
value >>= num;
// 任何数和3(11)&为任何数
return value & 3;
}
void TBSSetVaule(TwoBitSet *tbs, size_t x, size_t value)//设置值
{
assert(tbs);
// 计算x在数组里下标
size_t index = x >> 4;
// 因为两位表示一位,需要乘2
size_t num = x % 16 * 2;
if (value == 0)//设置为00
{
// 先将3左移到要设置的位,然后取反,保证在这两位为0,
// 然后&,这两位为0,其他为不变
tbs->bts[index] &= ~(3 << num);
}
// 要设置为01
else if (value == 1)
{
// 设置1
tbs->bts[index] |= (1 << num);
tbs->bts[index] &= ~(1 << (num + 1));
}
// 要设置为10
else if (value == 2)
{
// 设置1
tbs->bts[index] |= (1 << (num + 1));
// 设置0
tbs->bts[index] &= ~(1 << num);
}
// 要设置为11
else if (value == 3)
{
tbs->bts[index] |= (3 << num);
}
}
// 销毁
void BTSDestory(TwoBitSet *tbs)
{
assert(tbs);
free(tbs->bts);
tbs->bts = NULL;
tbs->range = 0;
}
2. 给两个文件,分别有 100 亿个整数,我们只有 1G
内存,如何找到两个文件交集?
512M
,文件 A
中数据出现一次将位图 1 中该位设置为 1,再出现不用设置B
中数据出现一次将位图 2 中该位设置为 1,再出现不用设置(一个整型 32 位,存 32 个数据),然后位图1 &
位图 2,&
结果为 1 的位则是两个文件的交集。512M
的内存,那我们就需要对该文件进行文件切分。具体操作就是:设置一种 / 多种哈希规则,将大文件进行切分成小的文件,那么相同的数字在同种哈希映射下会出现在相同的小文件中。然后分别对各个小文件进行找交集即可。主要考察文件分割。3. 位图应用变形:1 个文件有 100 亿个 int
,1G
内存,设计算法找到出现次数不超过 2 次的所有整数
1. 给两个文件,分别有 100亿 个 query
,我们只有 1G
内存,如何找到两个文件交集?分别给出精确算法和近似算法
2. 如何扩展布隆过滤器使得它支持删除元素的操作
++
,当删除数据时也不再是置 0,而是将计数器 --
,当位置为 0 时,说明该数据不存在。但是这个计数器到底设置多大呢?这得考虑到数据的大小,若设置为 int
将有 32
来表示数据存在与否,但是这样完全丧失了布隆过滤器的优点。若设置为 char
这样会很容易产生计数回绕,因为它只能保存 256 种情况。1. 给一个超过 100G
大小的 log file
,log
中存着 IP
地址, 设计算法找到出现次数最多的 IP
地址? 与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?
IP
地址是一个点分十进制,unsigned_int
,最简单粗暴的方法就是直接进行排序,设置 4G
内存对所有的 IP
地址进行标志,也就是拿 4G * 4 = 16G
个空间进行处理。在服务器上 16G
并不是什么难事情。但是效率就很低下了。IP
地址是一个 4 字节的整数,我们可以通过后 2 字节有 2^16
种可能性,将其分为 65536 个桶,然后 65536 个桶中会存在 65536 种可能性,这个可能性来自前 2 个字节。然后可以在 65536 个桶中查找到其桶内的出现次数最多的 IP
,然后再将这些进行再排序,就可以得到第一名。同理得到 TOP K
Linux
首先使用 sort
指令排序 log_file
,这样就可以得到一个升序排序,此时相同的 IP
将会相邻。再采用 uniq
指令去重,这里需要 uniq -c
将会将合并了多少行直接显示在前面,再使用 sort -nr
其中 sort -n
会对数字进行排序,即对合并后的行号大小进行排序。sort -nr
即对合并后行号进行降序排序。再采用 head - K
即可得到前 K
重复 IP
。综合就是:sort log_file | uniq -c | sort -nr | head -k
1. 给上千个文件,每个文件大小为 1K—100M
。给 n
个词,设计算法对每个词找到所有包含它的文件,你只有 100K
内存
n
个词找对应文件就是倒排索引。扫描所有文件,若该单词在某文件出现的话就在单词后面记录下文件号即可。这样扫描完毕所有的文件就能够解决这个问题。至于这 100M
的文件仅有 100K
的内存,只需要对文件进行切割就好了。Trie树
即哈希多叉树,字典树,单词查找树,前缀树,其用于在大量字符串中快速检索。
跳跃链表、跳跃表、跳表等相关海量数据问题。