有關海量數據處理的一直以來都是互聯網企業筆試面試的重點,此類題目也很是多,但概括起來,主要有如下3類:top K問題、重複問題、排序問題。如下將分別對這3類問題進行詳細的分析。ios
在大規模數據處理中,常常會遇到的一類問題:在海量數據中找出出現頻率最高的前K個數,或者從海量數據中找出最大的前K個數,這類問題一般被稱爲top K問題。例如,在搜索引擎中,統計搜索最熱門的10個查詢詞;在歌曲庫中統計下載率最高的前10首歌等。程序員
針對top K類問題,一般比較好的方案是分治+Trie樹/hash+小頂堆,即先將數據集按照Hash方法分解成多個小數據集,而後使用Trie樹或者Hash統計每一個小數據集中的query詞頻,以後用小頂堆求出每一個數據集中出頻率最高的前K個數,最後在全部top K中求出最終的top K。面試
例如:有1億個浮點數,如何找出其中最大的10000個?算法
最容易想到的方法是將數據所有排序,而後在排序後的集合中進行查找,最快的排序算法的時間複雜度通常爲O(nlogn),如快速排序。而在32位機器上,每一個float類型佔4個字節,1億個浮點數就要佔用400MB的存儲空間,對於一些可用內存小於400MB的計算機而言,很顯然是不能一次將所有數據讀入內存進行排序的。其實即便內存可以知足要求,該方法也並不高效,由於題目的目的是尋找出最大的10000個數便可,而排序倒是將全部的元素都排序了,作了不少無用功。數據庫
第二種方法爲局部淘汰法,該方法與排序方法相似,用一個容器保存前 10000個數,而後將剩餘的全部數字一一與容器內的最小數字相比,若是全部後續的元素都比容器內的 1000個數還小,那麼容器內的這 10000個數就是最大的 10000個數。若是某一後續元素比容器內的最小數字大,則刪掉容器內最小元素,並將該元素插入容器,最後遍歷完這1億個數,獲得的結果容器中保存的數即爲最終結果了。此時的時間複雜度爲O(n+m2),其中m爲容器的大小,即10000。數組
第三種方法是分治法,將1億個數據分紅100份,每份100萬個數據,找出每份數據中最大的10000個,最後在剩下的100×10000個數據裏面找出最大的10000個。若是100萬數據選擇足夠理想,那麼能夠過濾掉1億數據裏面99%的數據。100萬個數據裏面查找最大的10000個數據的方法以下:用快速排序的方法,將數據分爲2堆,若是大的那堆個數N大於10000個,繼續對大堆快速排序一次分紅2堆,若是大堆個數N小於10000,就在小的那堆裏面快速排序一次,找第10000-n大的數字;遞歸以上過程,就能夠找到第1w大的數。參考上面的找出第1w大數字,就能夠相似的方法找出前10000大數字了。此種方法每次須要的內存空間爲106×4=4MB,一共須要101次這樣的比較。多線程
第四種方法是Hash法。若是這1億個數裏面有不少重複的數,先經過Hash法,把這1億個數字去重複,這樣若是重複率很高的話,會減小很大的內存用量,從而縮小運算空間,而後經過分治法或最小堆法查找最大的10000個數。框架
第五種方法採用最小堆。首先讀入前10000個數來建立大小爲10000的小頂堆,建堆的時間複雜度爲O(mlogm)(m爲數組的大小即爲10000),而後遍歷後續的數字,並與堆頂(最小)數字進行比較。若是比最小的數小,則繼續讀取後續數字;若是比堆頂數字大,則替換堆頂元素並從新調整堆爲小頂堆。整個過程直至1億個數全都遍歷完爲止。而後按照中序遍歷的方式輸出當前堆中的全部10000個數字。該算法的時間複雜度爲O(nmlogm) ,空間複雜度是10000(常數)。socket
實際上,最優的解決方案應該是最符合實際設計需求的方案,在實際應用中,可能有足夠大的內存,那麼直接將數據扔到內存中一次性處理便可,也可能機器有多個核,這樣能夠採用多線程處理整個數據集。函數
下面針對不一樣的應用場景,分析了適合相應應用場景的解決方案。
(1)單機+單核+足夠大內存。
若是須要查找10億個查詢詞(每一個佔8B)中出現頻率最高的10個,考慮到每一個查詢詞佔8B,則10億個查詢詞所需的內存大約是109×8B=8GB內存。若是有這麼大的內存,直接在內存中對查詢詞進行排序,順序遍歷找出10個出現頻率最大的便可。這種方法簡單快速,更加實用。固然,也能夠先用HashMap求出每一個詞出現的頻率,而後求出頻率最大的10個詞。
(2)單機+多核+足夠大內存。
這時能夠直接在內存中使用Hash方法將數據劃分紅n個partition,每一個partition交給一個線程處理,線程的處理邏輯是同(1)相似,最後一個線程將結果歸併。
該方法存在一個瓶頸會明顯影響效率,即數據傾斜。每一個線程的處理速度可能不一樣,快的線程須要等待慢的線程,最終的處理速度取決於慢的線程。而針對此問題,解決的方法是,將數據劃分紅c×n個partition(c>1),每一個線程處理完當前partition後主動取下一個partition繼續處理,直到全部數據處理完畢,最後由一個線程進行歸併。
(3)單機+單核+受限內存。
這種狀況下,須要將原數據文件切割成一個一個小文件,如採用hash(x)%M,將原文件中的數據切割成M小文件,若是小文件仍大於內存大小,繼續採用Hash的方法對數據文件進行切割,直到每一個小文件小於內存大小,這樣每一個文件可放到內存中處理。採用(1)的方法依次處理每一個小文件。
(4)多機+受限內存。
這種狀況下,爲了合理利用多臺機器的資源,可將數據分發到多臺機器上,每臺機器採用(3)節中的策略解決本地的數據。可採用hash+socket方法進行數據分發。
從實際應用的角度考慮,(1)(2)(3)(4)方案並不可行,由於在大規模數據處理環境下,做業效率並非首要考慮的問題,算法的擴展性和容錯性纔是首要考慮的。算法應該具備良好的擴展性,以便數據量進一步加大(隨着業務的發展,數據量加大是必然的)時,在不修改算法框架的前提下,可達到近似的線性比;算法應該具備容錯性,即當前某個文件處理失敗後,能自動將其交給另一個線程繼續處理,而不是從頭開始處理。
top K問題很適合採用MapReduce框架解決,用戶只需編寫一個Map函數和兩個Reduce 函數,而後提交到Hadoop(採用Mapchain和Reducechain)上便可解決該問題。具體而言,就是首先根據數據值或者把數據hash(MD5)後的值按照範圍劃分到不一樣的機器上,最好可讓數據劃分後一次讀入內存,這樣不一樣的機器負責處理不一樣的數值範圍,實際上就是Map。獲得結果後,各個機器只需拿出各自出現次數最多的前N個數據,而後彙總,選出全部的數據中出現次數最多的前N個數據,這實際上就是Reduce過程。對於Map函數,採用Hash算法,將Hash值相同的數據交給同一個Reduce task;對於第一個Reduce函數,採用HashMap統計出每一個詞出現的頻率,對於第二個Reduce 函數,統計全部Reduce task,輸出數據中的top K便可。
直接將數據均分到不一樣的機器上進行處理是沒法獲得正確的結果的。由於一個數據可能被均分到不一樣的機器上,而另外一個則可能徹底彙集到一個機器上,同時還可能存在具備相同數目的數據。
Top K問題還有不少應用場景,尤爲是在程序員面試筆試中有不少實例,它們均可以採用上述方法解決。如下是一些歷年來常常被各大互聯網公司說起的該類問題。
(1)有10000000個記錄,這些查詢串的重複度比較高,若是除去重複後,不超過3000000個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就是越熱門。請統計最熱門的10個查詢串,要求使用的內存不能超過1GB。
(2)有10個文件,每一個文件1GB,每一個文件的每一行存放的都是用戶的query,每一個文件的query均可能重複。按照query的頻度排序。
(3)有一個1GB大小的文件,裏面的每一行是一個詞,詞的大小不超過16個字節,內存限制大小是1MB。返回頻數最高的100個詞。
(4)提取某日訪問網站次數最多的那個IP。
(5)10億個整數找出重複次數最多的100個整數。
(6)搜索的輸入信息是一個字符串,統計300萬條輸入信息中最熱門的前10條,每次輸入的一個字符串爲不超過255B,內存使用只有1GB。
(7)有1000萬個身份證號以及他們對應的數據,身份證號可能重複,找出出現次數最多的身份證號。
在海量數據中查找出重複出現的元素或者去除重複出現的元素也是常考的問題。針對此類問題,通常能夠經過位圖法實現。例如,已知某個文件內包含一些電話號碼,每一個號碼爲8位數字,統計不一樣號碼的個數。
本題最好的解決方法是經過使用位圖法來實現。8位整數能夠表示的最大十進制數值爲99999999。若是每一個數字對應於位圖中一個bit位,那麼存儲8位整數大約須要99MB。由於1B=8bit,因此99Mbit摺合成內存爲99/8=12.375MB的內存,便可以只用12.375MB的內存表示全部的8位數電話號碼的內容。
程序示例以下:
#include <iostream> #include <time.h> using namespace std; #define BITWORD 32 #define ARRNUM 100 int mmin = 10000000; int mmax = 99999999; int N = (mmax-mmin+1); #define BITS_PER_WORD 32 #define WORD_OFFSET(b) ((b) / BITS_PER_WORD) #define BIT_OFFSET(b) ((b) % BITS_PER_WORD) void SetBit(int *words, int n) { n -= mmin; words[WORD_OFFSET(n)] |= (1 << BIT_OFFSET(n)); } void ClearBit(int *words, int n) { words[WORD_OFFSET(n)] &= ~(1 << BIT_OFFSET(n)); } int GetBit(int *words, int n) { int bit = words[WORD_OFFSET(n)] & (1 << BIT_OFFSET(n)); return bit != 0; } int main( ) { int i; int j; int arr[ARRNUM]; int* words = new int[1 + N/BITS_PER_WORD]; if(words == NULL) { cout << "new error\n" << endl; exit(0); } int count = 0; for (i = 0; i < N; i++) { ClearBit(words, i); } srand( (unsigned)time( NULL ) ); printf("數組大小:%d\n", ARRNUM); for (j = 0; j < ARRNUM; j++) { arr[j]= rand( )%N; arr[j] += mmin; printf("%d\t", arr[j]); } for (j = 0; j < ARRNUM; j++) { SetBit(words, arr[j]); } printf("排序後a爲:\n"); for (i = 0; i < N; i++) { if (GetBit(words, i)) { printf("%d\t", i+mmin); count++; } } printf("總個數爲:%d\n",count); delete[] words; words = NULL; return 0; }
上例中,採用時間做爲種子,產生了100個隨機數,對這100個數進行位圖法排序,進而找出其中重複的數據。與此問題類似的面試筆試題還有:
(1)10億個正整數,只有1個數重複出現過,要求在O(n)的時間裏找出這個數。
(2)給定a、b兩個文件,各存放50億個url,每一個url各佔用64B,要求在O(n)的時間裏找出a、b文件共同的url。
(3)給40億個不重複的unsigned int的整數,沒排過序的,而後再給一個數,如何快速判斷這個數是否在那40億個數當中?
海量數據處理中一類常見的問題就是排序問題,即對海量數據中的數據進行排序。例如,一個文件中有9億條不重複的9位整數,對這個文件中的數字進行排序。
針對這個問題,最容易想到的方法是將全部數據導入到內存中,而後使用常規的排序方法,如插入排序、快速排序、歸併排序等各類排序方法對數據進行排序,最後將排序好的數據存入文件。但這些方法卻不能在此適用,因爲數據量巨大,在32位機器中,一個整數佔用4個字節,而9億條數據共佔用9×108×4B,大約須要佔用3.6GB內存,對於32位機器而言,很難將這麼多數據一次載入到內存,更不談進行排序了,因此此種方法通常不可行,須要考慮其餘方法。
方法一:數據庫排序法。將文本文件導入到數據庫中,讓數據庫進行索引排序操做後提取數據到文件。該種方法雖然操做簡單、方便,可是運算速度較慢,並且對數據庫設備要求比較高。
方法二:分治法。經過hash將9億條數據分爲20段,每段大約5000萬條,大約佔用5×106×4B=200MB空間,在文件中依次搜索0~5000萬,50000001~1億……將排序的結果存入文件。該方法要裝滿9位整數,一共須要20次,因此一共要進行20次排序,須要對文件進行20次讀操做。該方法雖然縮小了每次使用的內存空間大小,可是編碼複雜,速度也慢。
方案三:位圖法。考慮到最大的9位整數爲999999999,因爲9億條數據是不重複的,能夠把這些數據組成一個隊列或數組,讓它有0~999999999(一共10億個數)個元素數組下標表示數值,結點中用0表示沒有這個數,1表示存在這個數,判斷0或1只用一個bit存儲就夠了,而聲明一個能夠包含9位整數的bit數組,一共須要10億/8,大約120MB內存,把內存中的數據所有初始化爲0 ,讀取文件中的數據,並將數據放入內存。好比讀到一個數據爲341245909,那就先在內存中找到341245909這個bit,並將bit值置爲1 ,遍歷整個bit數組,將bit爲1的數組下標存入文件,最終獲得排序後的內容。
此類排序問題的求解方法通常都是採用上述方法。海量數據處理中與此相似的問題還有如下幾種:
(1)一年的全國高考考生人數爲500萬,分數使用標準分,最低100分,最高900分,不存在成績爲小數的狀況,把這500 萬考生的分數排序。
(2)一個包含n個正整數的文件,每一個正整數小於n,n等於1000萬,而且文件內的正整數沒有重複和關聯數據,輸出整數的升序排列。
轉載自新書《程序員面試筆試寶典》官網
本文連接地址: http://www.jobcoding.com/big-data/bigdata-sample/