源地址: http://blog.csdn.net/u013074465/article/details/40504281
何謂海量數據處理?
所謂海量數據處理,無非就是基於海量數據上的存儲、處理、操作。何謂海量,就是數據量太大,所以導致要麼是無法在較短時間內迅速解決,要麼是數據太大,導致無法一次性裝入內存。
那解決辦法呢?針對時間,我們可以採用巧妙的算法搭配合適的數據結構,如Bloom filter/Hash/bit-map/堆/數據庫或倒排索引/trie樹,針對空間,無非就一個辦法:大而化小,分而治之(hash映射),你不是說規模太大嘛,那簡單啊,就把規模大化爲規模小的,各個擊破不就完了嘛。
至於所謂的單機及集羣問題,通俗點來講,單機就是處理裝載數據的機器有限(只要考慮cpu,內存,硬盤的數據交互),而集羣,機器有多輛,適合分佈式處理,並行計算(更多考慮節點和節點間的數據交互)。
再者,通過本blog內的有關海量數據處理的文章:Big Data Processing,我們已經大致知道,處理海量數據問題,無非就是:
- 分而治之/hash映射 + hash統計 + 堆/快速/歸併排序;
- 雙層桶劃分
- Bloom filter/Bitmap;
- Trie樹/紅黑樹(set/map)/數據庫/倒排索引;
- 外排序(藉助堆);
- 分佈式處理之Hadoop/Mapreduce。
下面,本文第一部分、從set/map談到hashtable/hash_map/hash_set,簡要介紹下set/map/multiset/multimap,及hash_set/hash_map/hash_multiset/hash_multimap之區別(萬丈高樓平地起,基礎最重要),而本文第二部分,則針對上述那6種方法模式結合對應的海量數據處理面試題分別具體闡述。
第一部分、從set/map談到hashtable/hash_map/hash_set
稍後本文第二部分中將多次提到hash_map/hash_set,下面稍稍介紹下這些容器,以作爲基礎準備。一般來說,STL容器分兩種,
- 序列式容器(vector/list/deque/stack/queue/heap),
- 關聯式容器。關聯式容器又分爲set(集合)和map(映射表)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和multimap(多鍵映射表),這些容器均以RB-tree完成。此外,還有第3類關聯式容器,如hashtable(散列表),以及以hashtable爲底層機制完成的hash_set(散列集合)/hash_map(散列映射表)/hash_multiset(散列多鍵集合)/hash_multimap(散列多鍵映射表)。也就是說,set/map/multiset/multimap都內含一個RB-tree,而hash_set/hash_map/hash_multiset/hash_multimap都內含一個hashtable。
所謂關聯式容器,類似關聯式數據庫,每筆數據或每個元素都有一個鍵值(key)和一個實值(value),即所謂的Key-Value(鍵-值對)。當元素被插入到關聯式容器中時,容器內部結構(RB-tree/hashtable)便依照其鍵值大小,以某種特定規則將這個元素放置於適當位置。
包括在非關聯式數據庫中,比如,在MongoDB內,文檔(document)是最基本的數據組織形式,每個文檔也是以Key-Value(鍵-值對)的方式組織起來。一個文檔可以有多個Key-Value組合,每個Value可以是不同的類型,比如String、Integer、List等等。
{ "name" : "July",
"sex" : "male",
"age" : 23 }
set/map/multiset/multimap
set,同map一樣,所有元素都會根據元素的鍵值自動被排序,因爲set/map兩者的所有各種操作,都只是轉而調用RB-tree的操作行爲,不過,值得注意的是,兩者都不允許兩個元素有相同的鍵值。
不同的是:set的元素不像map那樣可以同時擁有實值(value)和鍵值(key),set元素的鍵值就是實值,實值就是鍵值,而map的所有元素都是pair,同時擁有實值(value)和鍵值(key),pair的第一個元素被視爲鍵值,第二個元素被視爲實值。
至於multiset/multimap,他們的特性及用法和set/map完全相同,唯一的差別就在於它們允許鍵值重複,即所有的插入操作基於RB-tree的insert_equal()而非insert_unique()。
hash_set/hash_map/hash_multiset/hash_multimap
hash_set/hash_map,兩者的一切操作都是基於hashtable之上。不同的是,hash_set同set一樣,同時擁有實值和鍵值,且實質就是鍵值,鍵值就是實值,而hash_map同map一樣,每一個元素同時擁有一個實值(value)和一個鍵值(key),所以其使用方式,和上面的map基本相同。但由於hash_set/hash_map都是基於hashtable之上,所以不具備自動排序功能。爲什麼?因爲hashtable沒有自動排序功能。
至於hash_multiset/hash_multimap的特性與上面的multiset/multimap完全相同,唯一的差別就是它們hash_multiset/hash_multimap的底層實現機制是hashtable(而multiset/multimap,上面說了,底層實現機制是RB-tree),所以它們的元素都不會被自動排序,不過也都允許鍵值重複。
所以,綜上,說白了,什麼樣的結構決定其什麼樣的性質,因爲set/map/multiset/multimap都是基於RB-tree之上,所以有自動排序功能,而hash_set/hash_map/hash_multiset/hash_multimap都是基於hashtable之上,所以不含有自動排序功能,至於加個前綴multi_無非就是允許鍵值重複而已。
此外,
OK,接下來,請看本文第二部分、處理海量數據問題之六把密匙。
第二部分、 六個密鑰
密匙一、分而治之/Hash映射 + Hash_map統計 + 堆/快速/歸併排序
10. 1000萬字符串,其中有些是重複的,需要把重複的全部去掉,保留沒有重複的字符串。請怎麼設計和實現?
- 方案1:這題用trie樹比較合適,hash_map也行。
- 方案2:from xjbzju:,1000w的數據規模插入操作完全不現實,以前試過在stl下100w元素插入set中已經慢得不能忍受,覺得基於hash的實現不會比紅黑樹好太多,使用vector+sort+unique都要可行許多,建議還是先hash成小文件分開處理再綜合。
- 1、hash_set在千萬級數據下,insert操作優於set? 這位blog:http://t.cn/zOibP7t給的實踐數據可靠不?
- 2、那map和hash_map的性能比較呢? 誰做過相關實驗?
- 3、那查詢操作呢,如下段文字所述?
或者小數據量時用map,構造快,大數據量時用hash_map?
rbtree PK hashtable
據朋友№邦卡貓№的做的紅黑樹和hash table的性能測試中發現:當數據量基本上int型key時,hash table是rbtree的3-4倍,但hash table一般會浪費大概一半內存。
因爲hash table所做的運算就是個%,而rbtree要比較很多,比如rbtree要看value的數據 ,每個節點要多出3個指針(或者偏移量) 如果需要其他功能,比如,統計某個範圍內的key的數量,就需要加一個計數成員。
密匙二、多層劃分
多層劃分----其實本質上還是分而治之的思想,重在“分”的技巧上!
適用範圍:第k大,中位數,不重複或重複的數字
基本原理及要點:因爲元素範圍很大,不能利用直接尋址表,所以通過多次劃分,逐步確定範圍,然後最後在一個可以接受的範圍內進行。
13、2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
有點像鴿巢原理,整數個數爲2^32,也就是,我們可以將這2^32個數,劃分爲2^8個區域(比如用單個文件代表一個區域),然後將數據分離到不同的區域,然後不同的區域在利用bitmap就可以直接解決了。也就是說只要有足夠的磁盤空間,就可以很方便的解決。
14、5億個int找它們的中位數。
- 思路一:這個例子比上面那個更明顯。首先我們將int劃分爲2^16個區域,然後讀取數據統計落到各個區域裏的數的個數,之後我們根據統計結果就可以判斷中位數落到那個區域,同時知道這個區域中的第幾大數剛好是中位數。然後第二次掃描我們只統計落在這個區域中的那些數就可以了。
實際上,如果不是int是int64,我們可以經過3次這樣的劃分即可降低到可以接受的程度。即可以先將int64分成2^24個區域,然後確定區域的第幾大數,在將該區域分成2^20個子區域,然後確定是子區域的第幾大數,然後子區域裏的數的個數只有2^20,就可以直接利用direct addr table進行統計了。
密匙三:Bloom filter/Bitmap
關於什麼是Bloom filter,請參看blog內此文:
適用範圍:可以用來實現數據字典,進行數據的判重,或者集合求交集基本原理及要點:
對於原理來說很簡單,位數組+k個獨立hash函數。將hash函數對應的值的位數組置1,查找時如果發現所有hash函數對應位都是1說明存在,很明顯這個過程並不保證查找的結果是100%正確的。同時也不支持刪除一個已經插入的關鍵字,因爲該關鍵字對應的位會牽動到其他的關鍵字。所以一個簡單的改進就是 counting Bloom filter,用一個counter數組代替位數組,就可以支持刪除了。
問題實例
BloomFIlter主要用於:
1 想對M條數據進行hash(比如用於判斷數據是否存在、進一步判重)。
2 然而每條數據比較大(K bit),導致內存(大小N bit < M*K)放不下。
3 但另一方面, N> M*S(s>8 比如說),意味着如果每條數據均攤下來它的bloom hash可以佔用Sbit,能夠得到較高的bloom filter 準確率。
密匙四、 樹一類counter結構 和 反向索引
Trie樹
適用範圍:數據量大,重複多,但是數據種類小可以放入內存
基本原理及要點:實現方式,節點孩子的表示方式
擴展:壓縮實現。
問題實例:
- 上面的第2題:尋找熱門查詢:查詢串的重複度比較高,雖然總數是1千萬,但如果除去重複後,不超過3百萬個,每個不超過255字節。
- 上面的第5題:有10個文件,每個文件1G,每個文件的每一行都存放的是用戶的query,每個文件的query都可能重複。要你按照query的頻度排序。
- 1000萬字符串,其中有些是相同的(重複),需要把重複的全部去掉,保留沒有重複的字符串。請問怎麼設計和實現?
- 上面的第8題:一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞。其解決方法是:用trie樹統計每個詞出現的次數,時間複雜度是O(n*le)(le表示單詞的平準長度),然後是找出出現最頻繁的前10個詞。
更多有關Trie樹的介紹,請參見此文:從Trie樹(字典樹)談到後綴樹。
反向索引
則是單詞指向了包含它的文檔,很容易看到這個反向的關係。
擴展:問題實例:文檔檢索系統,查詢那些文件包含了某單詞,比如常見的學術論文的關鍵字搜索。
關於倒排索引的應用,更多請參見:
密匙五、外排序
適用範圍:大數據的排序,去重
基本原理及要點:外排序的歸併方法,置換選擇敗者樹原理,最優歸併樹
問題實例:
1).有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16個字節,內存限制大小是1M。返回頻數最高的100個詞。
這個數據具有很明顯的特點,詞的大小爲16個字節,但是內存只有1M做hash明顯不夠,所以可以用來排序。內存可以當輸入緩衝區使用。
關於多路歸併算法及外排序的具體應用場景,請參見blog內此文:
密匙六、分佈式處理之Mapreduce
MapReduce是一種計算模型,簡單的說就是將大批量的工作(數據)分解(MAP)執行,然後再將結果合併成最終結果(REDUCE)。這樣做的好處是可以在任務被分解後,可以通過大量機器進行並行計算,減少整個操作的時間。但如果你要我再通俗點介紹,那麼,說白了,Mapreduce的原理就是一個歸併排序。
適用範圍:數據量大,但是數據種類小可以放入內存
基本原理及要點:將數據交給不同的機器去處理,數據劃分,結果歸約。
問題實例:
- The canonical example application of MapReduce is a process to count the appearances of each different word in a set of documents:
- 海量數據分佈在100臺電腦中,想個辦法高效統計出這批數據的TOP10。
- 一共有N個機器,每個機器上有N個數。每個機器最多存O(N)個數並對它們操作。如何找到N^2個數的中數(median)?
更多具體闡述請參見blog內: