海量问题解决方案

如何从大量的 URL 中找出相同的 URL?

给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。

提醒:每个URL占64B,那么50亿个URL占用的空间大小约为320GB。

分治策略

解答思路:遍历a和b文件,对每个url求hash然后求余数(hash(url)%1000),这个余数就是小文件编号,比如对a文件按照上述步骤分为1000个小文件,每个文件大约300M。

接着遍历a的每个小文件,没遍历到一个小文件把url存入set集合中,然后遍历b的每个小文件,看看是否存在set集合中,存在就添加到另一个文件中。

前缀树

一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用字典树(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。

如何从大量数据中找出高频词?

有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。

解答思路:分割文件同上。分割完成后遍历每个小文件用map统计每个单词出现的次数,然后建立大小为100的小顶堆,之后每次遍历次数的时候,如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为小顶堆,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。

如何统计不同电话号码的个数?

已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。

解答思路:对于本题,8 位电话号码可以表示的号码个数为 108 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 100M。

很明显我们用redis的bitmap来统计就行了,因为统计的是不同个数而不是求出具体不同的电话号码。用redis创建一个bitmap对象,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。

如何找出排名前 500 的数?

有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20*500 个数中找出前 500 的数?

解答思路:对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法:

首先建立大顶堆,堆的大小为数组的个数,即为 20,把每个数组最大的值存到堆中。

接着删除堆顶元素,保存到另一个大小为 500 的数组中,然后向大顶堆插入删除的元素所在数组的下一个元素。

重复上面的步骤,直到删除完第 500 个元素,也即找出了最大的前 500 个数。

如何在大量的数据中判断一个数是否存在?

给定 40 亿个不重复的没排过序的 unsigned int 型整数,然后再给定一个数,如何快速判断这个数是否在这 40 亿个整数当中?

解答思路:直接bitmap 由于 unsigned int 数字的范围是 [0, 1 << 32),我们用 1<<32=4,294,967,296 个 bit 来表示每个数字。初始位均为 0,那么总共需要内存:4,294,967,296b≈512M。

我们读取这 40 亿个整数,将对应的 bit 设置为 1。接着读取要查询的数,查看相应位是否为 1,如果为 1 表示存在,如果为 0 表示不存在。

如何按照 query 的频度排序?

有 10 个文件,每个文件大小为 1G,每个文件的每一行存放的都是用户的 query,每个文件的 query 都可能重复。要求按照 query 的频度排序。

解答思路:第一种方案就是如果 query 重复率高,说明不同 query 总数比较小,可以考虑把所有的 query 都加载到内存中的 map 中。接着就可以按照 query 出现的次数进行排序。第二种方案就是可以顺序遍历 10 个文件中的 query,通过 Hash 函数 hash(query) % 10 把这些 query 划分到 10 个小文件中。之后对每个小文件使用 map 统计 query 出现次数,根据次数排序并写入到零外一个单独文件中,即每个单独文件是按照词频排好序的,接着对所有文件按照 query 的次数进行排序,这里可以使用归并排序(由于无法把所有 query 都读入内存,因此需要使用外排序)。

如何找出某一天访问百度网站最多的 IP?

现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。

解答思路:这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。

注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可,max最大的那个ip即为答案。

如何查询最热门的查询串?

搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询串的长度不超过 255 字节。

假设目前有 1000w 个记录(这些查询串的重复度比较高,虽然总数是 1000w,但如果除去重复后,则不超过 300w 个)。请统计最热门的 10 个查询串,要求使用的内存不能超过 1G。(一个查询串的重复度越高,说明查询它的用户越多,也就越热门。)

解答思路:当这些字符串有大量相同前缀时,可以考虑使用前缀树来统计字符串出现的次数,树的结点保存字符串出现次数,0 表示没有出现。

在遍历字符串时,在前缀树中查找,如果找到,则把结点中保存的字符串次数加 1,否则为这个字符串构建新结点,构建完成后把叶子结点中字符串的出现次数置为 1。

最后依然使用小顶堆来对字符串的出现次数进行排序。