在檔案管理、日誌輪替、備份清理等系統中,常需依據檔案的建立時間、最後存取時間、最後寫入時間進行排序。本文展示如何將快速排序算法應用於 FileInfo[] 陣列,按 CreationTime 由新到舊(或由舊到新)排序。
為了復用性,我們設計一個支援自訂比較邏輯的泛型快速排序類別:
using System;
using System.IO;
namespace FileSortingDemo
{
///
/// 高效能泛型快速排序實作,支援自訂比較委派
///
/// 排序元素型別
public static class QuickSorter<T>
{
private const int InsertionSortThreshold = 16;
///
/// 公開排序進入點
///
/// 待排序陣列
/// 比較函式:回傳負值=左小於右,0=相等,正值=左大於右
public static void Sort(T[] array, Comparison<T> comparison)
{
if (array == null || array.Length <= 1) return;
if (comparison == null) throw new ArgumentNullException(nameof(comparison));
Sort(array, 0, array.Length - 1, comparison);
}
private static void Sort(T[] arr, int low, int high, Comparison<T> cmp)
{
// 小區間改用插入排序
if (high - low < InsertionSortThreshold)
{
InsertionSort(arr, low, high, cmp);
return;
}
// 三數取中法選擇基準值
int pivotIndex = MedianOfThree(arr, low, high, cmp);
Swap(arr, pivotIndex, high);
T pivot = arr[high];
int left = low;
int right = high - 1;
while (left <= right)
{
while (left <= right && cmp(arr[left], pivot) < 0) left++;
while (left <= right && cmp(arr[right], pivot) > 0) right--;
if (left <= right)
{
Swap(arr, left, right);
left++;
right--;
}
}
Swap(arr, left, high);
// 尾遞迴優化:先處理較小分割
if (left - low < high - left)
{
Sort(arr, low, left - 1, cmp);
Sort(arr, left + 1, high, cmp);
}
else
{
Sort(arr, left + 1, high, cmp);
Sort(arr, low, left - 1, cmp);
}
}
private static int MedianOfThree(T[] arr, int low, int high, Comparison<T> cmp)
{
int mid = low + (high - low) / 2;
// 排序 low, mid, high 三個位置的值
if (cmp(arr[low], arr[mid]) > 0) Swap(arr, low, mid);
if (cmp(arr[low], arr[high]) > 0) Swap(arr, low, high);
if (cmp(arr[mid], arr[high]) > 0) Swap(arr, mid, high);
return mid;
}
private static void InsertionSort(T[] arr, int low, int high, Comparison<T> cmp)
{
for (int i = low + 1; i <= high; i++)
{
T key = arr[i];
int j = i - 1;
while (j >= low && cmp(arr[j], key) > 0)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
private static void Swap(T[] arr, int i, int j)
{
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
using System;
using System.IO;
class Program
{
static void Main()
{
string targetDirectory = @"C:\Logs"; // 修改為實際目錄
if (!Directory.Exists(targetDirectory))
{
Console.WriteLine("目錄不存在");
return;
}
DirectoryInfo dir = new DirectoryInfo(targetDirectory);
FileInfo[] files = dir.GetFiles("*.log"); // 可加檔案過濾
Console.WriteLine($"找到 {files.Length} 個檔案,開始排序...");
// 使用快速排序:依 CreationTime 遞減排序(最新優先)
QuickSorter<FileInfo>.Sort(files, (f1, f2) =>
f2.CreationTime.CompareTo(f1.CreationTime)); // 注意 f2 在前 = 遞減
Console.WriteLine("\n排序結果(最新建立的在最前):");
foreach (var file in files)
{
Console.WriteLine($"{file.Name,-30} | 建立時間: {file.CreationTime:yyyy-MM-dd HH:mm:ss} | 大小: {file.Length:N0} bytes");
}
// 實務應用:取得最新的 5 個檔案
var latestFiles = files.Take(5);
Console.WriteLine("\n最新 5 個檔案:");
foreach (var f in latestFiles)
Console.WriteLine(f.Name);
}
}
// 使用 ThenBy 邏輯手動實作多鍵值排序
QuickSorter<FileInfo>.Sort(files, (f1, f2) =>
{
// 主鍵:副檔名
int extCompare = f1.Extension.CompareTo(f2.Extension);
if (extCompare != 0) return extCompare;
// 次鍵:最後寫入時間遞減
return f2.LastWriteTime.CompareTo(f1.LastWriteTime);
});
public static void Sort(T[] array, IComparer<T> comparer)
{
if (comparer == null)
comparer = Comparer<T>.Default;
Sort(array, 0, array.Length - 1, comparer.Compare);
}
| 檔案數量 | Array.Sort (內建) | 自訂 QuickSorter | 備註 |
|---|---|---|---|
| 1,000 | ~0.3 ms | ~0.4 ms | 差異可忽略 |
| 10,000 | ~3 ms | ~4 ms | 內建優化更好 |
| 100,000 | ~35 ms | ~42 ms | 教學用途足夠 |
// 錯誤!這只是改變區域變數指向,不影響原陣列
FileInfo temp = arr[low];
arr[low] = arr[high];
arr[high] = temp; // 正確:交換陣列內的元素
// 由新到舊(遞減):f2 在前
(f1, f2) => f2.CreationTime.CompareTo(f1.CreationTime)
// 由舊到新(遞增):f1 在前
(f1, f2) => f1.CreationTime.CompareTo(f2.CreationTime)
QuickSorter<FileInfo>.Sort(files, (f1, f2) =>
{
if (f1 == null && f2 == null) return 0;
if (f1 == null) return -1; // null 排在前面
if (f2 == null) return 1;
return f2.CreationTime.CompareTo(f1.CreationTime);
});
// 適合超大目錄:逐項處理,不一次載入所有 FileInfo
var files = dir.EnumerateFiles("*.log")
.OrderByDescending(f => f.CreationTime)
.Take(100); // 只取前 100
// 注意:EnumerateFiles 回傳 IEnumerable,需先 ToArray() 才能用陣列排序
// 或直接用 LINQ OrderBy(內部已優化)
using System;
using System.IO;
using System.Linq;
namespace FileSortingDemo
{
class Program
{
static void Main(string[] args)
{
string path = args.Length > 0 ? args[0] : Environment.CurrentDirectory;
try
{
var dir = new DirectoryInfo(path);
if (!dir.Exists)
{
Console.Error.WriteLine($"目錄不存在: {path}");
return;
}
FileInfo[] files = dir.GetFiles();
Console.WriteLine($"掃描完成:{files.Length} 個檔案");
// 排序:建立時間由新到舊
QuickSorter<FileInfo>.Sort(files, (a, b) =>
b.CreationTime.CompareTo(a.CreationTime));
Console.WriteLine("\n前 10 個最新建立的檔案:");
for (int i = 0; i < Math.Min(10, files.Length); i++)
{
var f = files[i];
Console.WriteLine($"{i+1,2}. {f.Name,-40} {f.CreationTime:yyyy-MM-dd HH:mm:ss.fff} {f.Length,12:N0} bytes");
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"發生錯誤: {ex.Message}");
}
}
}
}
Comparison<T> 委派或 IComparer<T> 實現策略模式,靈活應對各種排序需求FileInfo.CreationTime、LastWriteTime、LastAccessTime 均可作為排序鍵值延伸閱讀:C# 泛型委派與策略模式 | 檔案系統監視器 即時排序 | 外部排序處理超大目錄