2010-05-20 127 views
1

提高分揀文件的性能有了文件名給定的陣列,通過文件擴展名進行排序它最簡單了的方法是這樣的:通過擴展

Array.Sort(fileNames, 
    (x, y) => Path.GetExtension(x).CompareTo(Path.GetExtension(y))); 

的問題是,在很長的列表(〜800K )排序需要非常長的時間,而整個文件名的排序速度更快幾秒鐘!

理論,還有就是優化它的方式:而不是使用Path.GetExtension()和比較新創建的擴展,只有串,我們可以提供比從LastIndexOf('.')開始,而無需創建新的字符串的現有的文件名字符串比較比較。

現在,假設我找到了LastIndexOf('.'),我想重用本地.NET的StringComparer,並將其僅應用於LastIndexOf('.')之後的字符串部分,以保留所有文化考量。沒有找到辦法做到這一點。

任何想法?

編輯:

隨着tanascius的主意,用char.CompareTo()方法,我帶着我的尤伯杯快速文件擴展名-的Comparer,現在通過分類擴展快3個倍!它甚至比以某種方式使用Path.GetExtension()的所有方法更快。你怎麼看?

編輯2:

我發現,這個實現不考慮文化,因爲char.CompareTo()方法不考慮文化,所以這不是一個完美的解決方案。

任何想法?

public static int CompareExtensions(string filePath1, string filePath2) 
    { 
     if (filePath1 == null && filePath2 == null) 
     { 
      return 0; 
     } 
     else if (filePath1 == null) 
     { 
      return -1; 
     } 
     else if (filePath2 == null) 
     { 
      return 1; 
     } 

     int i = filePath1.LastIndexOf('.'); 
     int j = filePath2.LastIndexOf('.'); 

     if (i == -1) 
     { 
      i = filePath1.Length; 
     } 
     else 
     { 
      i++; 
     } 

     if (j == -1) 
     { 
      j = filePath2.Length; 
     } 
     else 
     { 
      j++; 
     } 

     for (; i < filePath1.Length && j < filePath2.Length; i++, j++) 
     { 
      int compareResults = filePath1[i].CompareTo(filePath2[j]); 

      if (compareResults != 0) 
      { 
       return compareResults; 
      } 
     } 

     if (i >= filePath1.Length && j >= filePath2.Length) 
     { 
      return 0; 
     } 
     else if (i >= filePath1.Length) 
     { 
      return -1; 
     } 
     else 
     { 
      return 1; 
     } 
    } 
+0

請參閱我編輯的答案...您可以通過實現更好的分支邏輯來改善您的代碼(在通用代碼路徑中的ifs更少)。要啓用一種文化,你必須將一個字符串轉換爲字符串,這將不幸再次放慢速度。 – tanascius 2010-05-21 11:48:46

回答

1

創建包含在ext.restofpath格式(或某種對/元組格式的,可​​以默認排序無需進一步變換的擴展名)各文件名的新的數組。對它進行排序,然後將其轉換回來。

這樣做更快,因爲不必爲每個元素多次檢索擴展(因爲您正在做類似N log N比較),您只需執行一次(然後再移回一次)。

1

您可以編寫一個比較器來比較擴展的每個字符。 char也有CompareTo()see here)。

基本上你循環,直到沒有留下更多字符中的至少一個字符串或一個CompareTo()返回一個值= 0。

編輯:!響應於OP

的的編輯比較器方法的性能可以顯着提高。請參閱以下代碼。此外,我增加了行

string.Compare(filePath1[i].ToString(), filePath2[j].ToString(), 
       m_CultureInfo, m_CompareOptions); 

,允許使用的CultureInfoCompareOptions。然而,與使用普通char.CompareTo()(約因素2)的版本相比,這會減慢一切。但是,根據我的own SO question這似乎是要走的路。

public sealed class ExtensionComparer : IComparer<string> 
{ 
    private readonly CultureInfo m_CultureInfo; 
    private readonly CompareOptions m_CompareOptions; 

    public ExtensionComparer() : this(CultureInfo.CurrentUICulture, CompareOptions.None) {} 

    public ExtensionComparer(CultureInfo cultureInfo, CompareOptions compareOptions) 
    { 
     m_CultureInfo = cultureInfo; 
     m_CompareOptions = compareOptions; 
    } 

    public int Compare(string filePath1, string filePath2) 
    { 
     if(filePath1 == null || filePath2 == null) 
     { 
      if(filePath1 != null) 
      { 
       return 1; 
      } 
      if(filePath2 != null) 
      { 
       return -1; 
      } 
      return 0; 
     } 

     var i = filePath1.LastIndexOf('.') + 1; 
     var j = filePath2.LastIndexOf('.') + 1; 

     if(i == 0 || j == 0) 
     { 
      if(i != 0) 
      { 
       return 1; 
      } 
      return j != 0 ? -1 : 0; 
     } 

     while(true) 
     { 
      if(i == filePath1.Length || j == filePath2.Length) 
      { 
       if(i != filePath1.Length) 
       { 
        return 1; 
       } 
       return j != filePath2.Length ? -1 : 0; 
      } 
      var compareResults = string.Compare(filePath1[i].ToString(), filePath2[j].ToString(), m_CultureInfo, m_CompareOptions); 
      //var compareResults = filePath1[i].CompareTo(filePath2[j]); 
      if(compareResults != 0) 
      { 
       return compareResults; 
      } 
      i++; 
      j++; 
     } 
    } 
} 

用法:

fileNames1.Sort(new ExtensionComparer(CultureInfo.GetCultureInfo("sv-SE"), 
        CompareOptions.StringSort)); 
0

這裏的主要問題是,你在呼喚Path.GetExtension多次爲每個路徑。如果這是快速排序,那麼您可以預期Path.GetExtension從日誌(n)到n次之間的任意位置調用,其中n是列表中每個項目在列表中的項目數。所以你將要緩存對Path.GetExtension的調用。

如果你使用LINQ我建議是這樣的:

filenames.Select(n => new {name=n, ext=Path.GetExtension(n)}) 
     .OrderBy(t => t.ext).ToArray(); 

這保證了Path.GetExtension只爲每個文件名調用一次。

1

不是最高效存儲,但最快的,根據我的測試:

SortedDictionary<string, List<string>> dic = new SortedDictionary<string, List<string>>(); 
foreach (string fileName in fileNames) 
{ 
    string extension = Path.GetExtension(fileName); 
    List<string> list; 
    if (!dic.TryGetValue(extension, out list)) 
    { 
     list = new List<string>(); 
     dic.Add(extension, list); 
    } 
    list.Add(fileName); 
} 
string[] arr = dic.Values.SelectMany(v => v).ToArray(); 

難道在800K隨機生成的文件名的8.3微型基準:

排序項使用LINQ到對象... 00: 00:04.4592595

物品分揀與SortedDictionary ... 00:00:02.4405325

排序項目進行的Array.Sort ... 00:00:06.6464205

+0

你確定關於'list'聲明和assigment?如果'TryGetValue'返回'true'會失敗嗎? – abatishchev 2010-05-20 22:46:53

+0

如果已經添加了擴展名,'TryGetValue'將返回true並且列表將被設置,所以我們很好地向它添加一個項目,否則我們只需創建一個新列表並將其添加到字典中。 – 2010-05-21 06:07:54