內容來源:抖音二面,內存只有 2G,如何對 100 億數據進行排序? (qq.com)
本文只是對博主文章進行簡單的理解,大部分內容都與原文相同
大數據小內存排序問題,很經典,很常見,類似的還有比如 “如何對上百萬考試的成績進行排序” 等等。
三種方法:
- 數據庫排序(對數據庫設備要求較高)
- 分治法(常見思路)
- 位圖法(Bitmap)
方法概要
-
數據庫排序(對數據庫設備要求較高)
操作:將數據全部導入數據庫,建立索引,數據庫對數據進行排序,提取出數據。
特點:操作簡單, 運算速度較慢,對數據庫設備要求較高。 -
分治法(常見思路)
操作:操作與歸並排序的思想類似,都是分治。
- 將數據進行分塊,然后對每個數據塊進行內部的排序(假如是對int形數據升序)。
- 和歸並排序類似,每個數據塊取第一個數據(當前塊的最小數據),然后比較取出的數據,取其最小加入結果集。
- 重復2操作,直到取完所有數據,此時排序完畢。
特點:
-
位圖法(Bitmap)
操作:基本思想就是利用一位(bit)代表一個數字,例如第 3 位上為 1,則說明 3 這個數字出現過,若為0,則說明 3 這個數字沒有出現過。很簡單~
java.util 封裝了
BitSet
這樣一個類,是位圖法的典型實現。特點:
-
可讀性差(不是一般的差 🤔)
-
位圖存儲的元素個數雖然比一般做法多,但是存儲的元素大小受限於存儲空間的大小。要想定義存儲空間大小就需要實現知道存儲的元素到底有多少
-
對於有符號類型的數據,需要用 2 位來表示,比如 第 0 位和第 1 位表示 0 這個數據,第 2 位和第 3 位表示 1 這個數據......,這會讓位圖能存儲的元素個數,元素值大小上限減半
-
只知道元素是否出現,無法知道出現的具體次數
-
文章內容轉載
大數據小內存排序問題,很經典,很常見,類似的還有比如 “如何對上百萬考試的成績進行排序” 等等
大概有這么三種方法:
- 數據庫排序(對數據庫設備要求較高)
- 分治法(常見思路)
- 位圖法(Bitmap)
1. 數據庫排序
將存儲着 100 億數據的文本文件一條一條導入到數據庫中,然后根據某個字段建立索引,數據庫進行索引排序操作后我們就可以依次提取出數據追加到結果集中。
這種方法的特點就是操作簡單, 運算速度較慢,對數據庫設備要求較高
2. 分治法
假設 100 億個數據都是 int 類型的數字
1 個 int 類型占 4 個字節(byte,B)
1 B = 8 位(bit)
1024 B(1024 B = 1KB) = 8 * 1024 bit
1024 * 1024 KB(1024KB = 1MB)= 1024 * 8 * 1024 bit
100 億 int 型數字就是 100 億 x 4B = 400 億 B = 38146.97265625 MB 約等於 37.25GB
100 億個 int 型數字大概占 37 個 G,2G 內存顯然一次性是裝不下的。
最常見的思路,拆分成一個一個的小文件來處理唄,最終再合並成一個排好序的大文件。
典型的分治法
1)把這個 37 GB 的大文件,用哈希或者直接平均分成若個小文件(比如 1000 個,每個小文件平均 38 MB 左右)
2)拆分完了之后,得到 1000 個 30 多 MB 的小文件,那么就可以放進內存里排序了,可以用快速排序,歸並排序,堆排序等等
3)1000 個小文件內部排好序之后,就要把這些內部有序的小文件,合並成一個大的文件,可以用堆排序來做 1000 路合並的操作(假設是從小到大排序,用小頂堆):
- 首先遍歷 1000 個文件,每個文件里面取第一個數字,組成
(數字, 文件號)
這樣的組合加入到堆里,遍歷完后堆里有 1000 個(數字,文件號)
這樣的元素 - 然后不斷從堆頂 pop 元素追加到結果集,每 pop 一個元素,就根據它的文件號去對應的文件里,補蟲一個元素進入堆中,直到那個文件中的元素被拿完
- 按照上面的操作,直到堆被取空了,此時最終結果文件里的全部數字就是有序的了
3. 位圖法(Bitmap)
位圖法的基本思想就是利用一位(bit)代表一個數字,例如第 3 位上為 1,則說明 3 這個數字出現過,若為0,則說明 3 這個數字沒有出現過。很簡單~
上面分析過,1M = 8388608 bit(800 多萬)
也就是說,通過位圖法,只需要 1M 的空間,我們就可以處理 800 多萬級別的數據
Java 中沒有 bit 這樣的基本數據類型,最小數據類型是 byte
,我們可以用 byte 數組來實現這個位圖法
byte 數組上的每一個元素都是 byte 類型,一個 byte 等於 8 個 bit,我們可以把 10 進制的 byte 用二進制的 bit 來表示,如下圖:
這樣,byte 數組中的一個元素就能表示 8 個數字是否出現過,比如 byte[0] 可以表示 0 ~ 7 是否出現過,byte[1] 可以表示 8 ~ 15 是否出現過.....
全部處理完之后,我們從前往后遍歷一遍 byte 數組就能獲取到有序數據了,時間復雜度為 O(N)
java.util 封裝了 BitSet
這樣一個類,是位圖法的典型實現
底層用的 long 數組,一個 long 型數據占 8 個字節(64 位,也就是說 long 數組中的一個元素就可以表示 64 個數字否出現過),占比與只占 1 個字節的 byte(8 位) 來說,能存儲的數據更多了
BitSet bitSet = new BitSet();
bitSet.set(0, 2, true);
上面的代碼的含義是,第 [0,2)
位會被設置成 1,也就是說這個類會自動地生成一個 long 型的元素,二進制表示是 .....(省略 60 個 0) 0011
,十進制表示就是 3
位圖法的缺點:
- 可讀性差(不是一般的差 🤔)
- 位圖存儲的元素個數雖然比一般做法多,但是存儲的元素大小受限於存儲空間的大小。要想定義存儲空間大小就需要實現知道存儲的元素到底有多少
- 對於有符號類型的數據,需要用 2 位來表示,比如 第 0 位和第 1 位表示 0 這個數據,第 2 位和第 3 位表示 1 這個數據......,這會讓位圖能存儲的元素個數,元素值大小上限減半