為什麼我們要儘量避免FileSort(檔案排序)
故事
現在,假設閱讀此文的你穿越回了小學二年級的時光,此時的你正在不斷的追求著隔壁班的班長小紅,恨不得把家裡所有東西都送給TA。那麼問題來了,如果你要把家裡東西都搬光送給小紅,你有幾種辦法?以下是我想到
- 一件一件的搬,如果搬不動那就拆分(不排除你被你父母揍一頓的可能性)
- 試圖通過吃藥讓自己變成大力士
上述例子看似滑稽,但其實這是一直以來人類解決大規模數量問題的解決方案,即要麼提升自身的能力以應付大規模的數量,要麼進行拆分,分而治之。
對應到IT行業,由於傳統小型機處理能力有限於是便有了大型機。如果不用大型機那咋辦嗎?只好拆分服務,於是便有了微服務。
經典面試題
面試官:假設你只有100M的記憶體可用,現在有一個大小為1G的檔案,裡面存放著整數,每個整數用4個位元組來儲存,要你對這個這個檔案中資料進行排序,你有什麼解決方案?
我:打電話找行政的妹子跟她要一條8G
的DDR4記憶體條,為了表示感謝順便約她去吃飯,說不定還能順利脫單。
面試官:emmm…..,回去等通知吧
解決方案
我:要解決這個問題,首先我們需要分為兩種情況:
資料不重複 如果資料不重複我們可以使用點陣圖來標記相應的資料,在需要輸出結果的時候遍歷點陣圖即可(此方案較為簡單,不在本文的討論範圍內)
資料重複 由於只有100M的記憶體可用,完全利用這100M記憶體的情況下意味著我們一次可以對26214400個整數(100 * 1024 * 1024 / 4 ) 進行排序,這意味著我們要分次讀取檔案並對讀取的內容進行排序,並將每一次排序的結果儲存到檔案系統中,之後再對這些檔案進行合併。
面試官:可以用畫圖表示一下嗎?
我:過程如下圖所示
面試官:可以,要不你現場寫一下程式碼吧
解決方案的實現
解決方案的實現總的來說有以下幾步
根據緩衝區的大小讀入相應的資料量,並把他們轉為整數陣列,進行排序,並寫入檔案,重複這一步直到原始資料檔案中沒有資料可讀。
合併這些已排序的檔案直到只剩一個檔案
將問題拆分開來看的話,我們需要解決以下子問題
- 由於我們採用4個位元組的資料來儲存整數,因此我們需要解決整數按位元組存取的問題
你可以考慮一下為什麼我們要用四個位元組來存取整數?而不是將其轉為字串
- 合併已排序的檔案的演演算法
方案一、預讀取一部分的資料寫入快取中,然後進行歸併排序(拆分之後的檔案中的資料都是有序的),當資料用完時再去檔案中讀取,重複此步驟直到沒有資料可讀
方案二、每次只從兩個檔案中讀取一個整數,進行比較,然後將較大/較小(取決於你要增序還是降序)的資料寫入檔案中
方案一,相對來說比較簡單並且速度比較快留給大家實現。
對於方案二,由於最近開發中有涉及狀態機,因此對於方案二我採用了狀態機的設計模式來實現。
該狀態機如下所示
給大夥提供個參考,我實現的方案還有進一步優化的空間?
測試
為了有一個直觀的印象,我們對一個16MB的檔案進行排序,緩衝區設定為512kb.
以下為測試結果
-
檔案分割階段,可以看出檔案分割的時候所用時間都是差不多的
-
合併階段,可以看出合併已排序的檔案所用的耗時是不斷遞增的因為併合並的檔案體積在不斷的遞增
如果我們直接將緩衝區設定為16MB呢?以下為測試結果,連合並階段都不用了。