算法對於前端工程師來講總有一層神祕色彩,這篇文章經過解讀V8源碼,帶你探索Array.prototype.sort
函數下的算法實現。html
來,先把你用過的和據說過的排序算法都列出來:前端
答題環節到了, sort 函數使用的以上哪種算法?java
若是你在網上搜索過關於 sort 源碼的文章,可能會告訴你數組長度小於10用插入排序,不然用快速排序。git
開始我也是這麼認爲的,可當我帶着答案去 GitHub 驗證的時候發現並不是如此。github
首先我並無找到對應的 js 源碼(文章說實現邏輯是用js寫的),由於但新版本的V8源碼已經修改,改用V8 Torque
。V8 Torque
是專門用來開發V8而創造的語言,語法相似TypeScript(再一次證實TypeScript的價值),它的編譯器使用CodeStubAssembler
轉換成高效的彙編代碼。
簡單理解起來就是創造了一個相似TypeScript的高效的高級語言,這個語言的文件後綴是tq
。面試
這裏須要感謝 justjavac 大神指點~算法
其次當我開始閱讀源碼的時候,並無找到使用快速排序的代碼,也沒有找到判斷數組長度的常數值10。數組
全部的證據代表,以前的源碼解讀文章極可能已通過時。前端工程師
那麼最新版本的 V8 用的是什麼排序算法呢?函數
V8引擎在xx版本以後就捨棄了快速排序,由於它不是穩定的排序算法,在最壞狀況下,時間複雜度會降級到O(n^2)。
而是採用了一種混合排序的算法:TimSort。
這種功能算法最初用於Python語言中,嚴格地說它t不屬於以上10種排序算法中的任何一種,屬於一種混合排序算法:
在數據量小的子數組中使用插入排序,而後再使用歸併排序將有序的子數組進行合併排序,時間複雜度爲 O(nlogn) 。
結合V8源碼,具體實現步驟以下:
/thrid_party/v8/builtins/array-sort.tq
1386 ArrayPrototypeSort 1403 ArrayTimSort 1369 ArrayTimSortImpl 1260 ComputeMinRunLength // 計算 minRunLength // while循環 1262 CountAndMakeRun // 計算當前 run 的長度 1267 BinaryInsertionSort // 用插入排序補足 run 長度 1274 MergeCollapse // 放入 pendingRuns 並根據須要進行調整 // 循環結束 1281 MergeForceCollapse // 合併 pendingRuns 中全部 run
tq語言雖然有些不同,但若是有TypeScript基礎的話閱讀起來應該不成問題。
下面重點解讀3個函數的源碼:
// 在while循環以前調用,每次排序只調用一次,用來計算 minRunLength macro ComputeMinRunLength(nArg: Smi): Smi { let n: Smi = nArg; let r: Smi = 0; assert(n >= 0); // 不斷除以2,獲得結果在 32~64 之間 while (n >= 64) { r = r | (n & 1); n = n >> 1; } const minRunLength: Smi = n + r; assert(nArg < 64 || (32 <= minRunLength && minRunLength <= 64)); return minRunLength; }
// 計算第一個 run 的長度 macro CountAndMakeRun(implicit context: Context, sortState: SortState)( lowArg: Smi, high: Smi): Smi { assert(lowArg < high); // 這裏保存的纔是咱們傳入的數組數據 const workArray = sortState.workArray; const low: Smi = lowArg + 1; if (low == high) return 1; let runLength: Smi = 2; const elementLow = UnsafeCast<JSAny>(workArray.objects[low]); const elementLowPred = UnsafeCast<JSAny>(workArray.objects[low - 1]); // 調用比對函數來比對數據 let order = sortState.Compare(elementLow, elementLowPred); const isDescending: bool = order < 0 ? true : false; let previousElement: JSAny = elementLow; // 遍歷子數組並計算 run 的長度 for (let idx: Smi = low + 1; idx < high; ++idx) { const currentElement = UnsafeCast<JSAny>(workArray.objects[idx]); order = sortState.Compare(currentElement, previousElement); if (isDescending) { if (order >= 0) break; } else { if (order < 0) break; } previousElement = currentElement; ++runLength; } if (isDescending) { ReverseRange(workArray, lowArg, lowArg + runLength); } return runLength; }
// 調整 pendingRuns ,使棧長度大於3時,全部 run 都知足 run[n]>run[n+1]+run[n+2] 且 run[n+1]>run2[n+2] transitioning macro MergeCollapse(context: Context, sortState: SortState) { const pendingRuns: FixedArray = sortState.pendingRuns; while (GetPendingRunsSize(sortState) > 1) { let n: Smi = GetPendingRunsSize(sortState) - 2; if (!RunInvariantEstablished(pendingRuns, n + 1) || !RunInvariantEstablished(pendingRuns, n)) { if (GetPendingRunLength(pendingRuns, n - 1) < GetPendingRunLength(pendingRuns, n + 1)) { --n; } MergeAt(n); // 將第 n 個 run 和第 n+1 個 run 進行合併 } else if ( GetPendingRunLength(pendingRuns, n) <= GetPendingRunLength(pendingRuns, n + 1)) { MergeAt(n); // 將第 n 個 run 和第 n+1 個 run 進行合併 } else { break; } } }
下次面試前端崗位的時候,若是面試官問你算法題,你能夠莞爾一笑地問他/她:
知道 Array 的 sort 函數使用了什麼算法嗎?TimSort要不要了解一下?
固然若是所以搞得面試官難堪而致使拿不到offer可別怪做者~
參考:
一部由衆多技術專家推薦,幫你成爲具備全面能力和全局視野工程師的進階利器——《了不得的JavaScript工程師》已經在京東、噹噹、淘寶各大平臺上架了~
點擊下方連接即刻踏上屬於你的進階之路吧!