[multi]set/map/table/hash 及海量數據相關問題

源地址: http://blog.csdn.net/u013074465/article/details/40504281


何謂海量數據處理?

   所謂海量數據處理,無非就是基於海量數據上的存儲、處理、操作。何謂海量,就是數據量太大,所以導致要麼是無法在較短時間內迅速解決,要麼是數據太大,導致無法一次性裝入內存。

    那解決辦法呢?針對時間,我們可以採用巧妙的算法搭配合適的數據結構,如Bloom filter/Hash/bit-map/堆/數據庫或倒排索引/trie樹,針對空間,無非就一個辦法:大而化小,分而治之(hash映射),你不是說規模太大嘛,那簡單啊,就把規模大化爲規模小的,各個擊破不就完了嘛。

    至於所謂的單機及集羣問題,通俗點來講,單機就是處理裝載數據的機器有限(只要考慮cpu,內存,硬盤的數據交互),而集羣,機器有多輛,適合分佈式處理,並行計算(更多考慮節點和節點間的數據交互)。

    再者,通過本blog內的有關海量數據處理的文章:Big Data Processing,我們已經大致知道,處理海量數據問題,無非就是:

  1. 分而治之/hash映射 + hash統計 + 堆/快速/歸併排序;
  2. 雙層桶劃分
  3. Bloom filter/Bitmap;
  4. Trie樹/紅黑樹(set/map)/數據庫/倒排索引;
  5. 外排序(藉助堆);
  6. 分佈式處理之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_無非就是允許鍵值重複而已

    此外,

  • 關於什麼hash,請看blog內此篇文章
  • 關於紅黑樹,請參看blog內系列文章
  • 關於hash_map的具體應用:請看這裏,關於hash_set:請看此文

    OK,接下來,請看本文第二部分、處理海量數據問題之六把密匙。


第二部分、 六個密鑰

密匙一、分而治之/Hash映射 + Hash_map統計 + 堆/快速/歸併排序

10. 1000萬字符串,其中有些是重複的,需要把重複的全部去掉,保留沒有重複的字符串。請怎麼設計和實現?

  • 方案1:這題用trie樹比較合適,hash_map也行。
  • 方案2:from xjbzju:,1000w的數據規模插入操作完全不現實,以前試過在stl下100w元素插入set中已經慢得不能忍受,覺得基於hash的實現不會比紅黑樹好太多,使用vector+sort+unique都要可行許多,建議還是先hash成小文件分開處理再綜合。
    上述方案2中讀者xbzju的方法讓我想到了一些問題,即是set/map,與hash_set/hash_map的性能比較?共計3個問題,如下:
  • 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的數量,就需要加一個計數成員。

    且1s rbtree能進行大概50w+次插入,hash table大概是差不多200w次。不過很多的時候,其速度可以忍了,例如倒排索引差不多也是這個速度,而且單線程,且倒排表的拉鍊長度不會太大。正因爲基於樹的實現其實不比hashtable慢到哪裏去,所以數據庫的索引一般都是用的B/B+樹,而且B+樹還對磁盤友好(B樹能有效降低它的高度,所以減少磁盤交互次數)。比如現在非常流行的NoSQL數據庫,像MongoDB也是採用的B樹索引。關於B樹系列,請參考本blog內此篇文章:從B樹、B+樹、B*樹談到R 樹。更多請待後續實驗論證。

密匙二、多層劃分

多層劃分----其實本質上還是分而治之的思想,重在“分”的技巧上!
  適用範圍:第k大,中位數,不重複或重複的數字
  基本原理及要點:因爲元素範圍很大,不能利用直接尋址表,所以通過多次劃分,逐步確定範圍,然後最後在一個可以接受的範圍內進行。

13、2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
    有點像鴿巢原理,整數個數爲2^32,也就是,我們可以將這2^32個數,劃分爲2^8個區域(比如用單個文件代表一個區域),然後將數據分離到不同的區域,然後不同的區域在利用bitmap就可以直接解決了。也就是說只要有足夠的磁盤空間,就可以很方便的解決。

14、5億個int找它們的中位數。

  1. 思路一:這個例子比上面那個更明顯。首先我們將int劃分爲2^16個區域,然後讀取數據統計落到各個區域裏的數的個數,之後我們根據統計結果就可以判斷中位數落到那個區域,同時知道這個區域中的第幾大數剛好是中位數。然後第二次掃描我們只統計落在這個區域中的那些數就可以了。
    實際上,如果不是int是int64,我們可以經過3次這樣的劃分即可降低到可以接受的程度。即可以先將int64分成2^24個區域,然後確定區域的第幾大數,在將該區域分成2^20個子區域,然後確定是子區域的第幾大數,然後子區域裏的數的個數只有2^20,就可以直接利用direct addr table進行統計了。


密匙三:Bloom filter/Bitmap

http://blog.csdn.net/v_july_v/article/details/6685894

關於什麼是Bloom filter,請參看blog內此文:

  適用範圍:可以用來實現數據字典,進行數據的判重,或者集合求交集
  基本原理及要點:

  對於原理來說很簡單,位數組+k個獨立hash函數。將hash函數對應的值的位數組置1,查找時如果發現所有hash函數對應位都是1說明存在,很明顯這個過程並不保證查找的結果是100%正確的。同時也不支持刪除一個已經插入的關鍵字,因爲該關鍵字對應的位會牽動到其他的關鍵字。所以一個簡單的改進就是 counting Bloom filter,用一個counter數組代替位數組,就可以支持刪除了。

問題實例

    給你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,則大大簡單了。 


BloomFIlter主要用於:

1 想對M條數據進行hash(比如用於判斷數據是否存在、進一步判重)。

2 然而每條數據比較大(K bit),導致內存(大小N bit < M*K)放不下。 

3 但另一方面, N> M*S(s>8 比如說),意味着如果每條數據均攤下來它的bloom hash可以佔用Sbit,能夠得到較高的bloom filter 準確率。


密匙四、 樹一類counter結構 和 反向索引

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樹(字典樹)談到後綴樹

反向索引

則是單詞指向了包含它的文檔,很容易看到這個反向的關係。

  擴展:
  問題實例:文檔檢索系統,查詢那些文件包含了某單詞,比如常見的學術論文的關鍵字搜索。

    關於倒排索引的應用,更多請參見:


密匙五、外排序

  適用範圍:大數據的排序,去重
  基本原理及要點:外排序的歸併方法,置換選擇敗者樹原理,最優歸併樹
問題實例:
  1).有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16個字節,內存限制大小是1M。返回頻數最高的100個詞。
  這個數據具有很明顯的特點,詞的大小爲16個字節,但是內存只有1M做hash明顯不夠,所以可以用來排序。內存可以當輸入緩衝區使用。

    關於多路歸併算法及外排序的具體應用場景,請參見blog內此文:


密匙六、分佈式處理之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:
  2. 海量數據分佈在100臺電腦中,想個辦法高效統計出這批數據的TOP10。
  3. 一共有N個機器,每個機器上有N個數。每個機器最多存O(N)個數並對它們操作。如何找到N^2個數的中數(median)?

    更多具體闡述請參見blog內:

發佈了114 篇原創文章 · 獲贊 15 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章