海量数据问题的处理-六种解决思路
1. 处理海量数据问题的四板斧
分治
基本上处理海量数据的问题,分治思想都是能够解决的,只不过⼀般情况下不会是最优⽅案,但可以作为⼀个baseline,可以逐渐优化⼦问题来达到⼀个较优解。传统的归并排序就是分治思想,涉及到⼤量⽆法加载到内存的⽂件、排序等问题都可以⽤这个⽅法解决。
适⽤场景:数据量⼤⽆法加载到内存
技能链接:归并排序
哈希(Hash)
个⼈感觉Hash是最为粗暴的⼀种⽅式,但粗暴却⾼效,唯⼀的缺点是耗内存,需要将数据全部载⼊内存。
适⽤场景:快速查,需要总数据量可以放⼊内存
bit(位集或BitMap)
位集这种思想其实简约⽽不简单,有很多扩展和技巧。⽐如多位表⽰⼀个数据(能够表⽰存在和数量问题),BloomFilter(布隆过滤器就是⼀个典型的扩展),在实际⼯作中应⽤场景很多,⽐如消息过滤等,读者需要掌握,但对于布隆过滤器使⽤有⼀些误区和不清楚的地⽅,读者可以看下⾯这篇博客避免这些性能上的误区。
适⽤场景:可进⾏数据的快速查,判重
技能链接:布隆过滤器使⽤的性能误区
堆(Heap)
堆排序是⼀种⽐较通⽤的TopN问题解决⽅案,能够满⾜绝⼤部分的求最值的问题,读者需要掌握堆的基本操作和思想。
适⽤场景:处理海量数据中TopN的问题(最⼤或最⼩),要求N不⼤,使得堆可以放⼊内存
技能链接:排序算法-Heap排序
2. 常见场景题:
谈⼀谈,分布式集中如何保证线程安全?
请你设计⼀种⽅案,给每个组分配不同的IP段,并且可以快速得知某个IP是哪个组的?
如何将⼀个⽂件快速下发到100万个服务器
这⾥有1000个任务,分给10个⼈做,你会怎样分配,先在纸上写个最简单的版本,然后优化。
全局队列,把1000任务放在⼀个队列⾥⾯,然后每个⼈都是取,完成任务。
分为10个队列,每个⼈分别到⾃⼰对应的队列中去取务。
如果让你来开发抢红包,说说你的思路是怎么样的?可能遇到什么问题,你会怎么解决
悲观锁,乐观锁,存储过程放在mysql数据库中。
有⼀个外卖平台,平台有很多外卖商家,但是平台上的⼴告位有限,如何使⽤有限的⼴告位来推⼴这些商家,假如平台每天会从10000个商家⾥⾯推荐50个商家放在⼴告为,每个商家有⼀个权值,你如何来做推荐?第⼆天怎么更新推荐的商家?
密匙⼀、分⽽治之/Hash映射 + Hash_map统计 + 堆/快速/归并排序
1. 海量⽇志数据,提取出某⽇访问百度次数最多的那个IP。
既然是海量数据处理,那么可想⽽知,给我们的数据那就⼀定是海量的。针对这个数据的海量,我们如何着⼿呢?对的,⽆⾮就是分⽽治之/hash映射 + hash统计 + 堆/快速/归并排序,说⽩了,就是先映射,⽽后统计,最后排序:
分⽽治之/hash映射:针对数据太⼤,内存受限,只能是:把⼤⽂件化成(取模映射)⼩⽂件,即16字⽅针:⼤⽽化⼩,各个击破,缩⼩规模,逐个解决hash_map统计:当⼤⽂件转化了⼩⽂件,那么我们便可以采⽤常规的hash_map(ip,value)来进⾏频率统计。
堆/快速排序:统计完了之后,便进⾏排序(可采取堆排序),得到次数最多的IP。
具体⽽论,则是: “⾸先是这⼀天,并且是访问百度的⽇志中的IP取出来,逐个写⼊到⼀个⼤⽂件中。注意到IP是32位的,最多有个2^32个IP。同样可以采⽤映射的⽅法,⽐如%1000,把整个⼤⽂件映射为1000个⼩⽂件,再出每个⼩⽂中出现频率最⼤的IP(可以采⽤hash_map对那1000个⽂件中的所有IP进⾏频率统计,然后依次出各个⽂件中频率最⼤的那个IP)及相应的频率。然后再在这1000个最⼤的IP中,出那个频率最⼤的IP,即为所求。”--⼗道海量数据处理⾯试题与⼗个⽅法⼤总结。
关于本题,还有⼏个问题,如下:
Hash取模是⼀种等价映射,不会存在同⼀个元素分散到不同⼩⽂件中的情况,即这⾥采⽤的是mod1000算法,那么相同的IP在hash取模后,只可能落在同⼀个⽂件中,不可能被分散的。因为如果两个IP相等,那么经过Hash(IP)之后的哈希值是相同的,将此哈希值取模(如模1000),必定仍然相等。
那到底什么是hash映射呢?简单来说,就是为了便于计算机在有限的内存中处理big数据,从⽽通过⼀种映射散列的⽅式让数据均匀分布在对应的内存位置(如⼤数据通过取余的⽅式映射成⼩树存放在内存中,或⼤⽂件映射成多个⼩⽂件),⽽这个映射散列⽅式便是我们通常所说的hash函数,设计的好的hash函数能让数据均匀分布⽽减少冲突。尽管数据映射到了另外⼀些不同的位置,但数据还是原来的数据,只是代替和表⽰这些原始数据的形式发⽣了变化⽽已。
OK,有兴趣的,还可以再了解下⼀致性hash算法,见blog内此⽂第五部分blog.csdn/v_july_v/article/details/6879101。
2. 寻热门查询,300万个查询字符串中统计最热门的10个查询
原题:搜索引擎会通过⽇志⽂件把⽤户每次检索使⽤的所有检索串都记录下来,每个查询串的长度为1-255字节。假设⽬前有⼀千万个记录(这些查询串的重复度⽐较⾼,虽然总数是1千万,但如果除去重复后,不超过3百万个。⼀个查询串的重复度越⾼,说明查询它的⽤户越多,也就是越热门),请你
统计最热门的10个查询串,要求使⽤的内存不能超过1G。
解答:
由上⾯第1题,我们知道,数据⼤则划为⼩的,如⼀亿个Ip求Top 10,可先%1000将ip分到1000个⼩⽂件中去,并保证⼀种ip只出现在⼀个⽂件中,再对每个⼩⽂件中的ip进⾏hashmap计数统计并按数量排序,最后归并或者最⼩堆依次处理每个⼩⽂件的top10以得到最后的结。
但如果数据规模⽐较⼩,能⼀次性装⼊内存呢?⽐如这第2题,虽然有⼀千万个Query,但是由于重复度⽐较⾼,因此事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把他们都放进内存中去(300万个字符串假设没有重复,都是最⼤长度,那么最多占⽤内存3M*1K/4=0.75G。所以可以将所有字符串都存放在内存中进⾏处理),⽽现在只是需要⼀个合适的数据结构,在这⾥,HashTable绝对是我们优先的选择。
所以我们放弃分⽽治之/hash映射的步骤,直接上hash统计,然后排序。So,针对此类典型的TOP K问题,采取的对策往往是:hashmap + 堆。如下所⽰:hash_map统计:先对这批海量数据预处理。具体⽅法是:维护⼀个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取⼀个Query,如果该字串不在Table中,那么加⼊该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加⼀即可。最终我们在O(N)的时间复杂度
内⽤Hash表完成了统计;
堆排序:第⼆步、借助堆这个数据结构,出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查和调整/移动。因此,维护⼀个K(该题⽬中是
10)⼤⼩的⼩根堆,然后遍历300万的Query,分别和根元素进⾏对⽐。所以,我们最终的时间复杂度是:O(N) + N' * O(logK),(N为1000万,N’为300万)。
别忘了这篇⽂章中所述的堆排序思路:“维护k个元素的最⼩堆,即⽤容量为k的最⼩堆存储最先遍历到的k个数,并假设它们即是最⼤的k个数,建堆费时O(k),并调整堆(费时O(logk))后,有k1>k2>...kmin(kmin设为⼩顶堆中最⼩元素)。继续遍历数列,每次遍历⼀个元素x,与堆顶元素⽐较,若x>kmin,则更新堆(x⼊堆,⽤时
logk),否则不更新堆。这样下来,总费时O(k logk+(n-k)logk)=O(n*logk)。此⽅法得益于在堆中,查等各项操作时间复杂度均为logk。”--第三章续、Top K算法问题的实现。
当然,你也可以采⽤trie树,关键字域存该查询串出现的次数,没有出现为0。最后⽤10个元素的最⼩推来对出现频率进⾏排序。
3. 有⼀个1G⼤⼩的⼀个⽂件,⾥⾯每⼀⾏是⼀个词,词的⼤⼩不超过16字节,内存限制⼤⼩是1M。
返回频数最⾼的100个词。
由上⾯那两个例题,分⽽治之 + hash统计 + 堆/快速排序这个套路,我们已经开始有了屡试不爽的感觉。下⾯,再拿⼏道再多多验证下。请看此第3题:⼜是⽂件很⼤,⼜是内存受限,咋办?还能怎么办呢?⽆⾮还是:mysql面试题csdn
分⽽治之/hash映射:顺序读⽂件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个⼩⽂件(记为x0,x1,...x4999)中。这样每个⽂件⼤概是200k左右。如果其中的有的⽂件
超过了1M⼤⼩,还可以按照类似的⽅法继续往下分,直到分解得到的⼩⽂件的⼤⼩都不超过1M。
hash_map统计:对每个⼩⽂件,采⽤trie树/hash_map等统计每个⽂件中出现的词以及相应的频率。
堆/归并排序:取出出现频率最⼤的100个词(可以⽤含100个结点的最⼩堆)后,再把100个词及相应的频率存⼊⽂件,这样⼜得到了5000个⽂件。最后就是把这5000个⽂件进⾏归并(类似于归并排序)的过程了。
4. 海量数据分布在100台电脑中,想个办法⾼效统计出这批数据的TOP10。
如果每个数据元素只出现⼀次,⽽且只出现在某⼀台机器中,那么可以采取以下步骤统计出现次数TOP10的数据元素:
堆排序:在每台电脑上求出TOP10,可以采⽤包含10个元素的堆完成(TOP10⼩,⽤最⼤堆,TOP10⼤,⽤最⼩堆,⽐如求TOP10⼤,我们⾸先取前10个元素调整成最⼩堆,如果发现,然后扫描后⾯的数据,并与堆顶元素⽐较,如果⽐堆顶元素⼤,那么⽤该元素替换堆顶,然后再调整为最⼩堆。最后堆中的元素就是TOP10⼤)。
多个表格vlookup函数的使用方法求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,再利⽤上⾯类似的⽅法求出TOP10就可以了。
但如果同⼀个元素重复出现在不同的电脑中呢,如下例⼦所述:
这个时候,你可以有两种⽅法:
遍历⼀遍所有数据,重新hash取摸,如此使得同⼀个元素只出现在单独的⼀台电脑中,然后采⽤上⾯所说的⽅法,统计每台电脑中各个元素的出现次数出TOP10,继⽽组合100台电脑上的TOP10,出最终的TOP10。
或者,暴⼒求解:直接统计统计每台电脑中各个元素的出现次数,然后把同⼀个元素在不同机器中的出现次数相加,最终从所有数据中出TOP10。
5、有10个⽂件,每个⽂件1G,每个⽂件的每⼀⾏存放的都是⽤户的query,每个⽂件的query都可能重复。要求你按照query的频度排序。
⽅案1:直接上:
hash映射:顺序读取10个⽂件,按照hash(query)%10的结果将query写⼊到另外10个⽂件(记为a0,a1,..a9)中。这样新⽣成的⽂件每个的⼤⼩⼤约也1G(假设hash函数是随机的)。
hash_map统计:⼀台内存在2G左右的机器,依次对⽤hash_map(query, query_count)来统计每个query出现的次数。注:hash_map(query,query_count)是⽤来统计每个query的出现次数,不是存储他们的值,出现⼀次,则count+1。
堆/快速/归并排序:利⽤快速/堆/归并排序按照出现次数进⾏排序,将排序好的query和对应的query_c
out输出到⽂件中,这样得到了10个排好序的⽂件(记为)。最后,对这10个⽂件进⾏归并排序(内排序与外排序相结合)。根据此⽅案1,这⾥有⼀份实现:
除此之外,此题还有以下两个⽅法:
⽅案2:⼀般query的总量是有限的,只是重复的次数⽐较多⽽已,可能对于所有的query,⼀次性就可以加⼊到内存了。这样,我们就可以采⽤trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。
⽅案3:与⽅案1类似,但在做完hash,分成多个⽂件后,可以交给多个⽂件来处理,采⽤分布式的架构来处理(⽐如MapReduce),最后再进⾏合并。
6. 给定a、b两个⽂件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你出a、b⽂件共同的url?
可以估计每个⽂件的⼤⼩为5G×64=320G,远远⼤于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分⽽治之的⽅法。
分⽽治之/hash映射:遍历⽂件a,对每个url求取,然后根据所取得的值将url分别存储到1000个⼩⽂件(记为,这⾥漏写个了a1)中。这样每个⼩⽂件的⼤约为300M。遍历⽂件b,采取和a相同的⽅式将ur
l分别存储到1000⼩⽂件中(记为)。这样处理后,所有可能相同的url都在对应的⼩⽂件()中,不对应的⼩⽂件不可能有相同的url。然后我们只要求出1000对⼩⽂件中相同的url即可。
hash_set统计:求每对⼩⽂件中相同的url时,可以把其中⼀个⼩⽂件的url存储到hash_set中。然后遍历另⼀个⼩⽂件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到⽂件⾥⾯就可以了。
OK,此第⼀种⽅法:分⽽治之/hash映射 + hash统计 + 堆/快速/归并排序,再看最后4道题,如下:
7. 怎么在海量数据中出重复次数最多的⼀个?
⽅案:先做hash,然后求模映射为⼩⽂件,求出每个⼩⽂件中重复次数最多的⼀个,并记录重复次数。然后出上⼀步求出的数据中重复次数最多的⼀个就是所求(具体参考前⾯的题)。
8. 上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。
⽅案:上千万或上亿的数据,现在的机器的内存应该能存下。所以考虑采⽤hash_map/搜索⼆叉树/红⿊树等来进⾏统计次数。然后利⽤堆取出前N个出现次数最多的数据。
9. ⼀个⽂本⽂件,⼤约有⼀万⾏,每⾏⼀个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
⽅案1:如果⽂件⽐较⼤,⽆法⼀次性读⼊内存,可以采⽤hash取模的⽅法,将⼤⽂件分解为多个⼩⽂件,对于单个⼩⽂件利⽤hash_map统计出每个⼩⽂件中10个最常出现的词,然后再进⾏归并处理,出最终的10个最常出现的词。
⽅案2:通过hash取模将⼤⽂件分解为多个⼩⽂件后,除了可以⽤hash_map统计出每个⼩⽂件中10个最常出现的词,也可以⽤trie树统计每个词出现的次数,时间复杂度是O(n le)(le表⽰单词的平准长度),最终同样出出现最频繁的前10个词(可⽤堆来实现),时间复杂度是O(n lg10)。
10. 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?
⽅案1:这题⽤trie树⽐较合适,hash_map也⾏。
⽅案2:from xjbzju:,1000w的数据规模插⼊操作完全不现实,以前试过在stl下100w元素插⼊set中已经慢得不能忍受,觉得基于hash的实现不会⽐红⿊树好太多,使⽤
vector+sort+unique都要可⾏许多,建议还是先hash成⼩⽂件分开处理再综合。
php格式化时间戳上述⽅案2中读者xbzju的⽅法让我想到了⼀些问题,即是set/map,与hash_set/hash_map的性能⽐较?共计3个问题,如下:
hash_set在千万级数据下,insert操作优于set? 这位blog:给的实践数据可靠不?
那map和hash_map的性能⽐较呢? 谁做过相关实验?
那查询操作呢,如下段⽂字所述?
或者⼩数据量时⽤map,构造快,⼤数据量时⽤hash_map?
rbtree PK hashtable
据朋友№邦卡猫№的做的红⿊树和hash table的性能测试中发现:当数据量基本上int型key时,hash table是rbtree的3-4倍,但hash table⼀般会浪费⼤概⼀半内存。
因为hash table所做的运算就是个%,⽽rbtree要⽐较很多,⽐如rbtree要看value的数据 ,每个节点要
多出3个指针(或者偏移量) 如果需要其他功能,⽐如,统计某个范围内的key的数量,就需要加⼀个计数成员。
且1s rbtree能进⾏⼤概50w+次插⼊,hash table⼤概是差不多200w次。不过很多的时候,其速度可以忍了,例如倒排索引差不多也是这个速度,⽽且单线程,且倒排表的拉链长度不会太⼤。正因为基于树的实现其实不⽐hashtable慢到哪⾥去,所以数据库的索引⼀般都是⽤的B/B+树,⽽且B+树还对磁盘友好(B树能有效降低它的⾼度,所以减少磁盘交互次数)。⽐如现在⾮常流⾏的NoSQL数据库,像MongoDB也是采⽤的B树索引。关于B树系列,请参考本blog内此篇⽂章:从B树、B+树、B*树谈到R 树。更多请待后续实验论证。11. ⼀个⽂本⽂件,出前10个经常出现的词,但这次⽂件⽐较长,说是上亿⾏或⼗亿⾏,总之⽆法⼀次读⼊内存,问最优解。
⽅案1:⾸先根据⽤hash并求模,将⽂件分解为多个⼩⽂件,对于单个⽂件利⽤上题的⽅法求出每个⽂件件中10个最常出现的词。然后再进⾏归并处理,出最终的10个最常出现的词。
12. 100w个数中出最⼤的100个数。
⽅案 1:采⽤局部淘汰法。选取前100个元素,并排序,记为序列L。然后⼀次扫描剩余的元素x,与排好序的100个元素中最⼩的元素⽐,如果⽐这个最⼩的要⼤,那么把这个最⼩的元素删除,并把x利⽤插⼊排序的思想,插⼊到序列L中。依次循环,知道扫描了所有的元素。复杂度为O(100w100)。
⽅案 2:采⽤快速排序的思想,每次分割之后只考虑⽐轴⼤的⼀部分,知道⽐轴⼤的⼀部分在⽐100多的时候,采⽤传统排序算法排序,取前100个。复杂度为O(100w100)。
⽅案3:在前⾯的题中,我们已经提到了,⽤⼀个含100个元素的最⼩堆完成。复杂度为O(100w*lg100)。
接下来,咱们来看第⼆种⽅法,双层桶划分。
密匙⼆、多层划分
多层划分----其实本质上还是分⽽治之的思想,重在“分”的技巧上!
适⽤范围:第k⼤,中位数,不重复或重复的数字
基本原理及要点:因为元素范围很⼤,不能利⽤直接寻址表,所以通过多次划分,逐步确定范围,然后最后在⼀个可以接受的范围内进⾏。
问题实例:
13. 2.5亿个整数中出不重复的整数的个数,内存空间不⾜以容纳这2.5亿个整数。
有点像鸽巢原理,整数个数为232,也就是,我们可以将这232个数,划分为2^8个区域(⽐如⽤单个⽂件代表⼀个区域),然后将数据分离到不同的区域,然后不同的区域在利⽤bitmap就可以直接解决了。也就是说只要有⾜够的磁盘空间,就可以很⽅便的解决。
14. 5亿个int它们的中位数。
思路⼀:这个例⼦⽐上⾯那个更明显。⾸先我们将int划分为2^16个区域,然后读取数据统计落到各个区域⾥的数的个数,之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第⼏⼤数刚好是中位数。然后第⼆次扫描我们只统计落在这个区域中的那些数就可以了。hennessy
实际上,如果不是int是int64,我们可以经过3次这样的划分即可降低到可以接受的程度。即可以先将int64分成224个区域,然后确定区域的第⼏⼤数,在将该区域分成220个⼦区域,然后确定是⼦区域的第⼏⼤数,然后⼦区域⾥的数的个数只有2^20,就可以直接利⽤direct addr table进⾏统计了。
思路⼆:@绿⾊夹克衫:同样需要做两遍统计,如果数据存在硬盘上,就需要读取2次。
⽅法同基数排序有些像,开⼀个⼤⼩为65536的Int数组,第⼀遍读取,统计Int32的⾼16位的情况,也就是0-65535,都算作0,65536 - 131071都算作1。就相当于⽤该数除以65536。Int32 除以 65536的结果不会超过65536种情况,因此开⼀个长度为65536的数组计数就可以。每读取⼀个数,数组中对应的计数+1,考虑有负数的情况,需要将结果加32768后,记录在相应的数组内。
第⼀遍统计之后,遍历数组,逐个累加统计,看中位数处于哪个区间,⽐如处于区间k,那么0- k-1的区间⾥数字的数量sum应该<n/2(2.5亿)。⽽k+1 - 65535的计数和也<n/2,第⼆遍统计同上⾯的⽅法类似,但这次只统计处于区间k的情况,也就是说(x / 65536) + 32768 = k。统计只统计低16位的情况。并且利⽤刚才统计的sum,⽐如sum = 2.49亿,那么现在就是要在低16位⾥⾯100万个数(2.5亿-2.49亿)。这次计数之后,再统计⼀下,看中位数所处的区间,最后将⾼位和低位组合⼀下就是结果了。
密匙三:Bloom filter/Bitmap
Bloom filter
适⽤范围:可以⽤来实现数据字典,进⾏数据的判重,或者集合求交集
基本原理及要点:对于原理来说很简单,位数组+k个独⽴hash函数。将hash函数对应的值的位数组置1,查时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查的结果是100%正确的。同时也不⽀持删除⼀个已经插⼊的关键字,因为该关键字对应的位会牵动到其他的关键字。所以⼀个简单的改进就是 counting Bloom filter,⽤⼀个counter数组代替位数组,就可以⽀持删除了。
还有⼀个⽐较重要的问题,如何根据输⼊元素个数n,确定位数组m的⼤⼩及hash函数个数。当hash函数个数k=(ln2)(m/n)时错误率最⼩。在错误率不⼤于E的情况下,m⾄少要等于n lg(1/E)才能表⽰任意n个元素的集合。但m还应该更⼤些,因为还要保证bit数组⾥⾄少⼀半为0,则m应该>=nlg(1/E)lge ⼤概就是nlg(1/E)1.44倍(lg表⽰以2为底的对数)。
举个例⼦我们假设错误率为0.01,则此时m应⼤概是n的13倍。这样k⼤概是8个。
注意这⾥m与n的单位不同,m是bit为单位,⽽n则是以元素个数为单位(准确的说是不同元素的个数)。通常单个元素的长度都是有很多bit的。所以使⽤bloom filter内存上通常都是节省的。
扩展:
Bloom filter将集合中的元素映射到位数组中,⽤k(k为哈希函数个数)个映射位是否全1表⽰元素在不在这个集合中。Counting bloom filter(CBF)将位数组中的每⼀位扩展为⼀个counter,从⽽⽀持了元素的删除操作。Spectral Bloom Filter(SBF)将其与集合元素的出现次数关联。SBF采⽤counter中的最⼩值来近似表⽰元素的出现频率。
可以看下上⽂中的第6题:
“6、给你A,B两个⽂件,各存放50亿条URL,每条URL占⽤64字节,内存限制是4G,让你出A,B⽂件
共同的URL。如果是三个乃⾄n个⽂件呢?
根据这个问题我们来计算下内存的占⽤,4G=2^32⼤概是40亿8⼤概是340亿,n=50亿,如果按出错率0.01算需要的⼤概是650亿个bit。现在可⽤的是340亿,相差并不多,这样可能会使出错率上升些。另外如果这些urlip是⼀⼀对应的,就可以转换成ip,则⼤⼤简单了。
同时,上⽂的第5题:给定a、b两个⽂件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你出a、b⽂件共同的url?如果允许有⼀定的错误率,可以使⽤Bloom filter,4G内存⼤概可以表⽰340亿bit。将其中⼀个⽂件中的url使⽤Bloom filter映射为这340亿bit,然后挨个读取另外⼀个⽂件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有⼀定的错误率)。”
Bitmap
关于什么是Bitmap,请看blog内此⽂第⼆部分:
下⾯关于Bitmap的应⽤,可以看下上⽂中的第13题,以及另外⼀道新题:
如何删除mysql数据库“13、在2.5亿个整数中出不重复的整数,注,内存不⾜以容纳这2.5亿个整数。
⽅案1:采⽤2-Bitmap(每个数分配2bit,00表⽰不存在,01表⽰出现⼀次,10表⽰多次,11⽆意义)
进⾏,共需内存2^32 * 2 bit=1 GB内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
⽅案2:也可采⽤与第1题类似的⽅法,进⾏划分⼩⽂件的⽅法。然后在⼩⽂件中出不重复的整数,并排序。然后再进⾏归并,注意去除重复的元素。”
15、给40亿个不重复的unsigned int的整数,没排过序的,然后再给⼀个数,如何快速判断这个数是否在那40亿个数当中?
⽅案1:frome oo,⽤位图/Bitmap的⽅法,申请512M的内存,⼀个bit位代表⼀个unsigned int值。读⼊40亿个数,设置相应的bit位,读⼊要查询的数,查看相应bit位是否为1,为1表⽰存在,为0表⽰不存在。
06
密匙四、Trie树/数据库/倒排索引
Trie树
适⽤范围:数据量⼤,重复多,但是数据种类⼩可以放⼊内存
基本原理及要点:实现⽅式,节点孩⼦的表⽰⽅式
扩展:压缩实现。
问题实例:
1、上⾯的第2题:寻热门查询:查询串的重复度⽐较⾼,虽然总数是1千万,但如果除去重复后,不超过3百万个,每个不超过255字节。
2、上⾯的第5题:有10个⽂件,每个⽂件1G,每个⽂件的每⼀⾏都存放的是⽤户的query,每个⽂件的query都可能重复。要你按照query的频度排序。
3、1000万字符串,其中有些是相同的(重复),需要把重复的全部去掉,保留没有重复的字符串。请问怎么设计和实现?
4、上⾯的第8题:⼀个⽂本⽂件,⼤约有⼀万⾏,每⾏⼀个词,要求统计出其中最频繁出现的前10个词。其解决⽅法是:⽤trie树统计每个词出现的次数,时间复杂度是O(n*le)(le 表⽰单词的平准长度),然后是出出现最频繁的前10个词。
更多有关Trie树的介绍,请参见此⽂:从Trie树(字典树)谈到后缀树。
数据库索引
适⽤范围:⼤数据量的增删改查
基本原理及要点:利⽤数据的设计实现⽅法,对海量数据的增删改查进⾏处理。
关于数据库索引及其优化,更多可参见此⽂:
关于MySQL索引背后的数据结构及算法原理,这⾥还有⼀篇很好的⽂章:
关于B 树、B+ 树、B* 树及R 树,本blog内有篇绝佳⽂章:
倒排索引(Inverted index)
适⽤范围:搜索引擎,关键字查询
基本原理及要点:为何叫倒排索引?⼀种索引⽅法,被⽤来存储在全⽂搜索下某个单词在⼀个⽂档或者⼀组⽂档中的存储位置的映射。
以英⽂为例,下⾯是要被索引的⽂本:
T0 = "it is what it is"
T1 = "what is it"
T2 = "it is a banana"
我们就能得到下⾯的反向⽂件索引:
"a": {2}
"banana": {2}
"is": {0, 1, 2}
"it": {0, 1, 2}
"what": {0, 1}
检索的条件"what","is"和"it"将对应集合的交集。
正向索引开发出来⽤来存储每个⽂档的单词的列表。正向索引的查询往往满⾜每个⽂档有序频繁的全⽂查询和每个单词在校验⽂档中的验证这样的查询。在正向索引中,⽂档占据了中⼼的位置,每个⽂档指向了⼀个它所包含的索引项的序列。也就是说⽂档指向了它包含的那些单词,⽽反向索引则是单
词指向了包含它的⽂档,很容易看到这个反向的关系。
扩展:
问题实例:⽂档检索系统,查询那些⽂件包含了某单词,⽐如常见的学术论⽂的关键字搜索。
关于倒排索引的应⽤,更多请参见:
第⼆⼗三、四章:杨⽒矩阵查,倒排索引关键词Hash不重复编码实践,
第⼆⼗六章:基于给定的⽂档⽣成倒排索引的编码与实践。
密匙五、外排序
适⽤范围:⼤数据的排序,去重
基本原理及要点:外排序的归并⽅法,置换选择败者树原理,最优归并树
问题实例:
1).有⼀个1G⼤⼩的⼀个⽂件,⾥⾯每⼀⾏是⼀个词,词的⼤⼩不超过16个字节,内存限制⼤⼩是1M。返回频数最⾼的100个词。
这个数据具有很明显的特点,词的⼤⼩为16个字节,但是内存只有1M做hash明显不够,所以可以⽤来排序。内存可以当输⼊缓冲区使⽤。
关于多路归并算法及外排序的具体应⽤场景,请参见blog内此⽂:
第⼗章、如何给10^7个数据量的磁盘⽂件排序
密匙六、分布式处理之Mapreduce
MapReduce是⼀种计算模型,简单的说就是将⼤批量的⼯作(数据)分解(MAP)执⾏,然后再将结果合并成最终结果(REDUCE)。这样做的好处是可以在任务被分解后,可以通过⼤量机器进⾏并⾏计算,减少整个操作的时间。但如果你要我再通俗点介绍,那么,说⽩了,Mapreduce的原理就是⼀个归并排序。
适⽤范围:数据量⼤,但是数据种类⼩可以放⼊内存
基本原理及要点:将数据交给不同的机器去处理,数据划分,结果归约。
问题实例:
1、The canonical example application of MapReduce is a process to count the appearances of each different word in a set of documents:变龙ppt
2、海量数据分布在100台电脑中,想个办法⾼效统计出这批数据的TOP10。
3、⼀共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何到N^2个数的中数(median)?
更多具体阐述请参见blog内:
从Hadhoop框架与MapReduce模式中谈海量数据处理;
及MapReduce技术的初步了解与学习。
其它模式/⽅法论,结合操作系统知识
⾄此,六种处理海量数据问题的模式/⽅法已经阐述完毕。据观察,这⽅⾯的⾯试题⽆外乎以上⼀种或其变形,然题⽬为何取为是:秒杀99%的海量数据处理⾯试题,⽽不是100%呢。
OK,给读者看最后⼀道题,如下:
⾮常⼤的⽂件,装不进内存。每⾏⼀个int类型数据,现在要你随机取100个数。
我们发现上述这道题,⽆论是以上任何⼀种模式/⽅法都不好做,那有什么好的别的⽅法呢?我们可以看看:操作系统内存分页系统设计(说⽩了,就是映射+建索引)。
Windows 2000使⽤基于分页机制的虚拟内存。每个进程有4GB的虚拟地址空间。基于分页机制,这4GB地址空间的⼀些部分被映射了物理内存,⼀些部分映射硬盘上的交换⽂
件,⼀些部分什么也没有映射。程序中使⽤的都是4GB地址空间中的虚拟地址。⽽访问物理内存,需要使⽤物理地址。 关于什么是物理地址和虚拟地址,请看:
▶物理地址 (physical address): 放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个 地址每位的值就将相应地址的物理内存中放⼊数据总线上的内容。物理内存是以字节(8位)为单位编址的。
▶虚拟地址 (virtual address): 4G虚拟地址空间中的地址,程序中使⽤的都是虚拟地址。 使⽤了分页机制之后,4G的地址空间被分成了固定⼤⼩的页,每⼀页或者被映射到物理内存,或者被映射到硬盘上的交换⽂件中,或者没有映射任何东西。对于⼀ 般程序来说,4G的地址空间,只有⼀⼩部分映射了物理内存,⼤⽚⼤⽚的部分是没有映射任何东西。物理内存也被分页,来映射地址空间。对于32bit的 Wi
n2k,页的⼤⼩是4K字节。CPU⽤来把虚拟地址转换成物理地址的信息存放在叫做页⽬录和页表的结构⾥。
物理内存分页,⼀个物理页的⼤⼩为4K字节,第0个物理页从物理地址 0x00000000 处开始。由于页的⼤⼩为4KB,就是0x1000字节,所以第1页从物理地址 0x00001000 处开始。第2页从物理地址 0x00002000 处开始。可以看到由于页的⼤⼩是4KB,所以只需要32bit的地址中⾼20bit来寻址物理页。
返回上⾯我们的题⽬:⾮常⼤的⽂件,装不进内存。每⾏⼀个int类型数据,现在要你随机取100个数。针对此题,我们可以借鉴上述操作系统中内存分页的设计⽅法,做出如下解决⽅案:
操作系统中的⽅法,先⽣成4G的地址表,在把这个表划分为⼩的4M的⼩⽂件做个索引,⼆级索引。30位前⼗位表⽰第⼏个4M⽂件,后20位表⽰在这个4M⽂件的第⼏个,等等,基于key value来设计存储,⽤key来建索引。
但如果现在只有10000个数,然后怎么去随机从这⼀万个数⾥⾯随机取100个数?请读者思考。更多海⾥数据处理⾯试题,请参见此⽂第⼀部分:
不过,相信你也早就意识到,若单纯论海量数据处理⾯试题,本blog内的有关海量数据处理⾯试题的⽂章已涵盖了你能在⽹上所到的7080%。但有点,必须负责任的敬告⼤家:⽆论是这些
海量数据处理⾯试题也好,还是算法也好,⾯试时,7080%的⼈不是倒在这两⽅⾯,⽽是倒在基础之上(诸如语⾔,数据库,操作系统,⽹络协议等等),所以,⽆论任何时候,基础最重要,没了基础,便什么都不是。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论