本文共 9540 字,大约阅读时间需要 31 分钟。
缘由
由于有面试通知,现在复习一下十道和海量数据处理相关的题。两篇博客已经讲的非常完备了,但是我怕读懂了并非真的懂,所以必须自己复述一遍。
面试归类
下面6个方面覆盖了大多数关于海量数据处理的面试题: - 分而治之/hash映射 + hash统计 + 堆/快速/归并排序;
- 双层桶划分
- Bloom filter/Bitmap;
- Trie树/数据库/倒排索引;
- 外排序;
- 分布式处理之Hadoop/Mapreduce。
下面我讲针对上两篇博客里的海量数据处理的题的解法再复述一遍。 第一类:分治后hash统计再排序
第一题:海量日志数据,提取出某日访问百度次数最多的那个IP
解答: 该题解题思路总共分为三步 - 分而治之/hash映射:如果该文件过大,不能全部读入内存。我们就必须先利用hash函数将其分割成若干小文件。再分别对各个小文件进行处理。注意这一步我们肯定会将相同的ip放在同一个文件。由于这个题干给的比较少。我只有自己脑补一下,大概给我们的日志中就是百度web服务器自己记录的来自不同的ip的访问。这个日志是按天生成的,所以现在我们要统计一天内,哪一个ip访问本网站百度的次数最多。那么日志中肯定就是记录是访问ip和时间,但是相同的ip肯定是单独的一条。所以现在要将相同的ip放在同一个文件中处理。由于我们确定某个ip放到哪一个子文件的时候是使用的一个hash函数,对一个ip用hash函数求值后取模,就可以确定这个条ip记录放到哪一文件。所有在相同的ip一定存在一个文件,这样方便我们统计。
- hash_map统计:针对每一个文件,我们肯定是可以读入内存了。(因为这里每一个文件的大小是我们在第一步控制的)。针对内存中的每一条记录,以ip为键,以其出现的次数为值,遍历该文件中的每一条记录。(顺便深入思考一下:这里所谓的hash_map就是一个底层以hashtable实现的数据结构。我猜想底层也就是有一个巨大的数组,数组下标就是键的hash值,而数组的内容就是值。在这里就是用ip算出了数组下标,然后数组内容就是该ip出现的次数。当然更多内容需要去探索,比如这个数组大小是如何确定?发生冲突是如何解决。不过这点与该题无关了,我们只是利用hashmap这个数据结构。)
- 堆/快速排序:针对每一个文件,只需遍历一次就可以取得该文件,ip次数出现最多的那个ip。将所有小文件中访问次数最多的ip一对比,就可以得到整个文件中ip最多的那一个。
第二题:寻找热门查询,300万个查询字符串中统计最热门的10个查询
题干:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串,要求使用的内存不能超过1G。 解答: 有了一题的思路,那么这道第二题就更好理解了,思路肯定还是分而治之/hash映射 + hash统计 + 堆/快速/归并排序。但是我们需要考虑第一步是否有必要,因为共有记录300万条,就算每条记录的长度都是255字节,那么就是 (3000000*1/4)1024 约等于 (3000000*1/4)1000 ,结果单位为MB(注意1/4的单位是kb),那么就是0。75g。所以这整份文件是可以轻易读入大小为1G的内存的。所以第一步分而治之/hash映射在本题中是不需要的。 直接第二、三步: hash统计:遍历这1000万条记录,每天都以一个query为键,其出现的累计出现的次数为值。那么就可以在一次遍历的过程中将所有的统计query出现的次数统计完。 堆/快速/归并排序:由于我们只需要求出前10个query。所以也就是求top k的问题,那么使用堆结构。我们可以logK的时间内完成查找和调整堆结构。所以,我们只需要维护一个大小为10的最小堆。我们确实是求查询次数最多的10个次,所以我们该用最小堆。接着,就将先将300万条记录的前10个插入最小堆,调整最小堆,然后将第11个与最小堆的堆顶比较,如何堆顶元素大,那么不做任何改变,如果堆顶元素小,那么就用第11个替换掉堆顶元素,然后调整最小堆,与第12个进行比较。那么我们在一次遍历之后就可以选出最大的10个。 第三题
3、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。 解答: 显然过程还是分而治之/hash映射 + hash统计 + 堆/快速/归并排序。而且题中已经说的很明显了,就是内存肯定是不够用的。所以我们肯定要进行分而治之/hash映射这一步。我就不再复述了,因为和上面两道题和相似。 第四题
4、海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。 解答 还是分而治之/hash映射 + hash统计 + 堆/快速/归并排序 这个思想。 但是我觉得如果面试官如果问类似的题的话,一定要注意反问面试官,相同的数据是不是存在在同一台电脑上,还有没有没内存大小的限制(这个问题只要题中没有说明有什么限制,我都觉得需要问一下,体现自己思维比较全面)。针对这道题,如果相同的数据不在同一台电脑上,那么就先要进行分而治之/hash映射,将相同的文件放于同一台电脑上,有点像将相同的数据处于同一个文件中。其他进行的过程与本题其他内容类似,不再赘述。 第五题
5、有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。 解答: 本题不再是选出top N的问题。而是排序的问题,但是核心还是不变。 分而治之/hash映射:由于没有说相同的query是放在同一个文件,所以必须先对每一个query做一个映射,将相同的query放在同一个文件。所以注意这里会产生10个新的文件(相同query在同一个文件)。 hash统计:主要选一个2g的电脑来对每一个文件完成这个过程。因为每个文件的容量都为1g。 堆/快速/归并排序:先利用堆/快速/归并排序对每一份文件的query和query_cout的进行排序,然后将排好序的内容再输出到一份新文件中。最后对着10文件进行归并排序(内排序和外排序一起进行)原博客有一份实现:
- https://github.com/ooooola/sortquery/blob/master/querysort.py
- 本题一个要点就是:在最后一步的在做完hash,分成多个文件后,可以采用分布式的架构来处理(比如MapReduce)。我记得hadoop的第一个统计单词的个数的小例子就是读取不同文件的单词的次数,然后输出的结果就是一个文本,里面有每一个在单词在所有文件中出现的总次数。
第六题
6、 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 解答: 5000 000 000 * 64 约等于 5g*64 = 320G。显然一个内存装不去。所以需要分而治之/hash映射。 分而治之/hash映射:我们可以考虑将这个50亿个url分到1000个小文件中,这样每个文件也就是300m。主要在映射中,我们对a文件和b文件使用的是同一个hash函数,这样就能保证如何a文件和b文件中有相同的url,那么将会被映射到对应的文件中去。比如a0对应b0文件,a1对应b1文件... hash_set统计:第二步非常简单了,我们只需要对每一对小文件进行处理。先将a0文件的内容储存到hash_set中,再依次计算b0的,如果有相同的那就是a与b共有的文件,所以将该条url存储到最终结果的文件中既可。
第七题
7、怎么在海量数据中找出重复次数最多的一个? 解答: 简单说,就是hash映射成小文件,然后hash求出每个数据出现的次数,将每个文件中次数最多的一比较就可以了。 第八题
8、上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。 解答: 与第二题类似。 第九题
9、一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。 解答: 除了使用前面的分而治之/hash映射 + hash统计 + 堆/快速/归并排序的方法以外,也可以使用单词查找树。下面是单词查找树的分析: 这题是考虑时间效率。用trie树(单词查找树,字典树,就是一种树有插入、查找,删除等操作)统计每个词出现的次数,时间复杂度是O(n*le)(le表示单词的 字符串长度 )。因为在面对每一个单词的时候,我们都可以在字典书中进行查找,查看是否有这个单词,然后将相应的次数加一,之后就是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(n*lg10)。所以总的时间复杂度,是O(n*le)与O(n*lg10)中较大的哪一个。 第十题
10. 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现? 想了想和前面的题都类似的,即可用字典树,也可以用hashmap,原博客对该题还做了性能上的比较。有兴趣者自行参看。 第十一题
11. 一个文本文件,找出前10个经常出现的词,但这次文件比较长,说是上亿行或十亿行,总之无法一次读入内存,问最优解。 首先根据用hash并求模,将文件分解为多个小文件,对于单个文件利用上题的方法求出每个文件件中10个最常出现的词。然后再进行排序处理,找出最终的10个最常出现的词。 第十二题
12. 100w个数中找出最大的100个数。 最小堆直接秒杀,复杂度为O(100w*lg100)。 第二、三类:双层桶划分、Bloom filter/Bitmap。
双层桶划分、Bloom filter/Bitmap我发现两者实际上还是解决的同一个问题。双层桶划分有点难理解,我今天就算了。
Bloom filter我已经写过博客总结了:
bitmap
下面就是bitmap,参见百度百科的例子,我觉得这个东西很像计数排序。
假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复)。那么我们就可以采用Bit-map的方法来达到排序的目的。要表示8个数,我们就只需要8个Bit(1Bytes)(为什么是8个,因为最大的数是7,试想,如果最大的数是10,我们必须要11个bit,这是因为我们需要将需要排序的数作为下标),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0 然后遍历这5个元素,首先第一个元素是4,那么就把4对应的位置为1(可以这样操作 p+(i/8)|(0×01<<(i%8)) 当然了这里的操作涉及到Big-ending和Little-ending的情况,这里默认为Big-ending),因为是从零开始的,所以要把第五位置为1。 然后再处理第二个元素7,将第八位置为1,,接着再处理第三个元素,一直到最后处理完所有的元素,将相应的位置为1。 然后我们现在遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的。其实就是把计数排序用的统计数组的每个单位缩小成bit级别的布尔数组。 然后我们再看看利用这个三个技术能解决的问题,主要是bloom filter/Bitmap 第一题
给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢? 先考虑只有两个文件的情况下: 如果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有一定的错误率)。” 这只是另外的解法,我想了想,如果文件多至三个或者n个,那么还是使用分而治之/hash映射 + hash统计 + 堆/快速/归并排序中第六题的思路吧,三个文件的时候,我们用就让每一个小文件:a0、b0、c0。先让a0和b0生成两个hash_set,然后让对c0的每一条记录都去算hash,先a0比对,如果a0有,再b0比对。这样三个都比对成功的就是三个文件都有的url。
第二题
在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。 方案1:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^32 * 2 bit=1 GB内存,还可以接受(为什么这里取2^32,这是因为我们假设了最大的数就是2^32,实际上,这也是各大语言中默认的int类型最大的数。所以这2.5亿个数的取值范围必须在 0< 2^32之间)。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。我觉得思路倒是很清晰。 方案2:也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。”
第三题
已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。 8位最多99 999 999,最多需要99m个bit,那么99 999 999/(8*1024*1024) = 11.92,所以大概10几m字节的内存即可。 (可以理解为从0-99 999 999的数字,每个数字对应一个Bit位,所以只需要99M个Bit==12MBytes,这样,就用了小小的12M左右的内存表示了所有的8位数的电话)。注意只是用这个每一个bit位来表示有没有这个电话号码,而相应bit位的下标才表示了这个具体的电话号码。 第四题
给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中? 方案1:用位图/Bitmap的方法,那么需要多少内存呢?因为已经说了是unsigned int ,所以 最大的整数应该是:2^32 = 4 294 967 296。所以其给的40亿数字的范围就在 0-4 294 967 296之间。故需要 4 294 967 296 个bit位,4 294 967 296个bit就是512MB(4 294 967 296/(8*1024*1024))可得。所以申请512M的内存,一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。 第四五类:Trie树/数据库/倒排索引、外排序
有兴趣的可以自行看看博客:http://blog.csdn.net/v_july_v/article/details/7382693 Trie树
我在这里只说一下:Trie树。 在面对需要处理字符串的时候,不一定非要用hash来完成插入或者查找。也可以使用Trie字典树。所以在处理字符串的时候,我们都需要想到用字典树。字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。可以进行查找、删除、插入等操作。上面已经讲了的题中,有如下几个题涉及字典树: 上面的第2题:寻找热门查询:查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个,每个不超过255字节。 上面的第5题:有10个文件,每个文件1G,每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。要你按照query的频度排序。 在分而治之/Hash映射之后,对每一份文件里面的单词,插入到字典树中。在设计字典树的数据结构的时,对于每一个节点,我们需要一个额外的一个int字段,来存储这个单词出现的次数。第一次插入的时候当然是1,之后每被查找到,就自加一。 第一题
1000万字符串,其中有些是相同的(重复),需要把重复的全部去掉,保留没有重复的字符串。请问怎么设计和实现? 这个可能需要两个字典树,遇见一个插入字典数A,如果遇见在A中出现的,那么就插入字典数B。每次遇见新的字符串时,先查找B,如果在B中出现了,那么就不能插入A。如果B在中没有出现,在A中也没有,就插入A,如果B中没有,而在A中有,那么删除A中有的,并插入B。 第二题
也是上面的第8题:一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词。 其解决方法是:用trie树统计每个词出现的次数,时间复杂度是O(n*le)(le表示单词的平均长度),然后是找出出现最频繁的前10个词(使用最小堆)。 第六类:分布式处理之Mapreduce
说起Mapreduce常常和Hadoop联系起啦。july总结的很好: - Mapreduce是一种模式。
- Hadoop是一种框架。
- Hadoop是一个实现了mapreduce模式的开源的分布式并行编程框架。
- 在hadoop的框架上采取mapreduce的模式处理海量数据。
Mapreduce
那么下面来具体看看Mapreduce是怎样一种模式: 所谓mapreduc是两个单词map和reduce的组合。Map函数把大数据集进行分解操作得到两个或者更多的小“桶”。每台处理器对分割出来的每个桶进行操作,获取一组中间值,而Reduce函数是把这些中间结果通过一定的函数进行处理来获取最终的答案。 例子一
简单的例子就是: 假设我们有一组数据:1,2,3,…,100。求这一组数据的平方和。现在我们用MapReduce这个模型解决这一个问题。 首先我们把这组数据分成100份,交由100台计算机去处理。每一台计算机只做一件事,就是把自己要处理的数据平方一下。这样一来,最初的那组数[1,2,3,…,100]就被映射成了[1,4,9,16,…,10000]了。这就是所谓的Map操作。而Reduce操作呢?Reduce操作就是把映射后得到的这100个新的数据累加。 虽然这个例子过于简单,但是依然算是说明了问题。
例子二
下面一个例子是经常出现的,就是统计词频的例子,假设我们有这样三篇文章,每篇文章的内容如下: Paper1: We study algorithm. Paper2: We share our thinking. Paper3: This team shares thinking of algorithm. 我们想要统计的是所有文章的词频,那么我们可以将每一篇paper交由一台电脑计算词频(map),再对所有计算结果进行统计(reduce)。 所以map操作开始如下所示:
接着每台自己计算对自己的部分进行处理,如下所示:
最后得到结果:
对其中,我唯一比较困惑的就是,对中间结果的处理,july在旁边图上说:还是由多台处理器分别处理。我就很奇怪其的通信机制,或者如下
![](https://img-blog.csdn.net/20140628213409671)
到底是存在哪里?分别存在不同的机器上?那么机器2 的we 1 就会跑到机器1上的文档里么?对旁边的注释: hash%n我还是能理解的。但是我怎么觉得只有对所有机器的文档都遍历一遍,边遍历边算hash值再求模,映射到不同的文档中。 其实,我觉得都不用映射都不同的文档,直接在一份文档里不就好了么?然后再进行reduce,因为每一个文档的每一行都是同一个字符出现的次数。所以按我的想法,上图第C步,在一个文档中所示才更为合理。 - we 1 we 1
- study 1
- algorithm 1 algorithm 1
- share 1 share 1
- our 1
- Thinking 1 Thinking 1
- Team 1
- of 1
现在先做做了解吧,以后要深入学习的话,再说。 Hadoop
Hadoop既然是个框架,肯定涉及很多组件,我们来看看下面这幅图。
下面对这幅图的主要组件稍作解释: - 分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。
- HBase – Hadoop Database,首先是一个HBase是一个分布式的、面向列的开源数据库。作为一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,我们可以利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase利用Hadoop HDFS作为其文件存储系统。同时,HBase利用Hadoop MapReduce来处理HBase中的海量数据;HBase利用Zookeeper作为协同服务。
注意,下面两句话区分一下HDFS和Hbase(摘自:Hbase与HDFS是个什么关系呀? http://tieba.baidu.com/p/1338652682):
- HDFS是一种文件格式,像FAT32,NTFS之类的,属于底层的;HBase是数据库,可以建在HDFS上,也可以不在这上面,不过按照设计,是推荐运行在HDFS上的。
- hdfs只是一个存储空间,他的完整名字是分布式文件系统。有名可知他的作用了。hbase是一个内存数据库,简单点说hbase把表啊什么的存在hdfs上。
- MapReduce是一种编程模型,运行分布式系统之上。我的理解就是按照一定的模式处理着HBase或HDFS内的数据。
- Pig是一种编程语言,它简化了Hadoop常见的工作任务。Pig可加载数据、表达转换数据以及存储最终结果。Pig内置的操作使得半结构化数据变得有意义(如日志文件)。同时Pig可扩展使用Java中添加的自定义数据类型并支持数据转换。
- hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。 其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
- Sqoop是一个用来将Hadoop和关系型数据库中的数据相互转移的工具,可以将一个关系型数据库(例如 : MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。
总的来说,在我们编程的时候,主要是用java针对mapreduce来编程。上次参加一个讲座的时候老师仿佛也说了可以利用hive,使用sql来完成同样的工作。但是集群的安装配置也是值得掌握的。 后记
主要是总结了别人的,还需要更多地努力啊!加油