C# Sort 和 OrderBy 比较

Posted

技术标签:

【中文标题】C# Sort 和 OrderBy 比较【英文标题】:C# Sort and OrderBy comparison 【发布时间】:2010-12-22 09:44:56 【问题描述】:

我可以使用 Sort 或 OrderBy 对列表进行排序。哪个更快?都在做同样的事情 算法?

List<Person> persons = new List<Person>();
persons.Add(new Person("P005", "Janson"));
persons.Add(new Person("P002", "Aravind"));
persons.Add(new Person("P007", "Kazhal"));

1.

persons.Sort((p1,p2)=>string.Compare(p1.Name,p2.Name,true));

2.

var query = persons.OrderBy(n => n.Name, new NameComparer());

class NameComparer : IComparer<string>

    public int Compare(string x,string y)
    
      return  string.Compare(x, y, true);
    

【问题讨论】:

我不敢相信没有一个答案提到这一点,但最大的区别在于:OrderBy 是对 Array 或 List 进行排序的副本,而 Sort 实际上是对其进行排序。 正如标题所说的比较,我想补充一点,OrderBy 是稳定的,并且排序是稳定的,最多 16 个元素,因为如果元素多于 16 个元素,则使用插入排序,然后它会切换到其他不稳定的算法编辑:稳定意味着保持具有相同键的元素的相对顺序。 @PRMan 不,OrderBy 创建了一个惰性可枚举。只有在返回的枚举上调用诸如 ToList 之类的方法时,才会得到排序后的副本。 @Stewart,您不认为 System.Core/System/Linq/Enumerable.cs 中缓冲区中的 Array.Copy 或 Collection.Copy 到 TElement[] 是副本吗?而且,如果您在 IEnumerable 上调用 ToList,您可能会立即在内存中同时拥有 3 个副本。这对于非常大的数组来说是一个问题,这是我的观点的一部分。此外,如果您多次需要相同的排序顺序,则调用就地排序一次比重复排序列表更有效,因为它是永久性的。 @PRMan 哦,你的意思是内部构建了一个排序的副本。这仍然是不准确的,因为 OrderBy 不会创建副本 - 据我所知,这是由 GetEnumerator 方法在您实际开始遍历集合时完成的。我刚刚尝试单步执行我的代码,发现从 LINQ 表达式填充变量的代码几乎可以立即运行,但是当您进入 foreach 循环时,它会花时间对其进行排序。我想当我有更多时间时,我应该花一些时间来弄清楚它是如何在幕后工作的。 【参考方案1】:

我只想补充一点,orderby 更有用。

为什么?因为我可以这样做:

Dim thisAccountBalances = account.DictOfBalances.Values.ToList
thisAccountBalances.ForEach(Sub(x) x.computeBalanceOtherFactors())
thisAccountBalances=thisAccountBalances.OrderBy(Function(x) x.TotalBalance).tolist
listOfBalances.AddRange(thisAccountBalances)

为什么要复杂的比较器?只需根据字段进行排序。这里我是根据 TotalBalance 进行排序的。

很简单。

我不能用排序来做到这一点。我想知道为什么。使用 orderBy 就可以了。

至于速度,它总是 O(n)。

【讨论】:

问题:您的答案中的 O(n) 时间(我假设)是指 OrderBy 还是 Comparer?我不认为快速排序可以达到 O(N) 时间。【参考方案2】:

简而言之:

列表/数组排序():

不稳定的排序。 就地完成。 使用 Introsort/Quicksort。 自定义比较是通过提供一个比较器来完成的。如果比较代价高昂,它可能会比 OrderBy() 慢(允许使用键,见下文)。

OrderBy/ThenBy():

稳定排序。 不在原位。 使用快速排序。快速排序不是一种稳定的排序。诀窍是:排序时,如果两个元素具有相同的键,它会比较它们的初始顺序(在排序之前已存储)。 允许使用键(使用 lambda)根据元素的值对元素进行排序(例如:x =&gt; x.Id)。在排序之前首先提取所有键。与使用 Sort() 和自定义比较器相比,这可能会产生更好的性能。

来源: MDSN、reference source 和 dotnet/coreclr 存储库 (GitHub)。

上面列出的一些语句基于当前的 .NET 框架实现 (4.7.2)。将来可能会改变。

【讨论】:

【参考方案3】:

我认为注意SortOrderBy 之间的另一个区别很重要:

假设存在Person.CalculateSalary() 方法,需要花费大量时间;甚至可能超过对大列表进行排序的操作。

比较

// Option 1
persons.Sort((p1, p2) => Compare(p1.CalculateSalary(), p2.CalculateSalary()));
// Option 2
var query = persons.OrderBy(p => p.CalculateSalary()); 

选项 2 可能具有更优越的性能,因为它只调用 CalculateSalary 方法 n 次,而 Sort 选项可能调用 CalculateSalary 最多 @ 987654321@ 次,取决于排序算法的成功与否。

【讨论】:

这是真的,尽管有解决该问题的方法,即将数据保存在数组中并使用 Array.Sort 重载,该重载采用两个数组,一个是键,另一个是值.在填充键数组时,您将调用CalculateSalary n 次。这显然不如使用 OrderBy 方便。【参考方案4】:

Darin Dimitrov 的回答表明,OrderBy 在面对已经排序的输入时比List.Sort 稍快。我修改了他的代码,让它反复对未排序的数据进行排序,OrderBy 在大多数情况下会稍微慢一些。

此外,OrderBy 测试使用ToArray 来强制枚举 Linq 枚举器,但这显然返回了与输入类型 (List&lt;Person&gt;) 不同的类型 (Person[])。因此,我使用ToList 而不是ToArray 重新运行了测试,并得到了更大的差异:

Sort: 25175ms
OrderBy: 30259ms
OrderByWithToList: 31458ms

代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

class Program

    class NameComparer : IComparer<string>
    
        public int Compare(string x, string y)
        
            return string.Compare(x, y, true);
        
    

    class Person
    
        public Person(string id, string name)
        
            Id = id;
            Name = name;
        
        public string Id  get; set; 
        public string Name  get; set; 
        public override string ToString()
        
            return Id + ": " + Name;
        
    

    private static Random randomSeed = new Random();
    public static string RandomString(int size, bool lowerCase)
    
        var sb = new StringBuilder(size);
        int start = (lowerCase) ? 97 : 65;
        for (int i = 0; i < size; i++)
        
            sb.Append((char)(26 * randomSeed.NextDouble() + start));
        
        return sb.ToString();
    

    private class PersonList : List<Person>
    
        public PersonList(IEnumerable<Person> persons)
           : base(persons)
        
        

        public PersonList()
        
        

        public override string ToString()
        
            var names = Math.Min(Count, 5);
            var builder = new StringBuilder();
            for (var i = 0; i < names; i++)
                builder.Append(this[i]).Append(", ");
            return builder.ToString();
        
    

    static void Main()
    
        var persons = new PersonList();
        for (int i = 0; i < 100000; i++)
        
            persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
         

        var unsortedPersons = new PersonList(persons);

        const int COUNT = 30;
        Stopwatch watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            Sort(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            OrderBy(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            OrderByWithToList(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("OrderByWithToList: 0ms", watch.ElapsedMilliseconds);
    

    static void Sort(List<Person> list)
    
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    

    static void OrderBy(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    

    static void OrderByWithToList(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToList();
    

【讨论】:

我现在在 LinqPad 5 (.net 5) 中运行测试代码,OrderByWithToListOrderBy 花费的时间相同。【参考方案5】:

您应该计算 OrderBy 和 Sort 方法使用的算法的复杂度。 我记得 QuickSort 的复杂度为 n (log n),其中 n 是数组的长度。

我也搜索过 orderby,但即使在 msdn 库中也找不到任何信息。 如果您没有任何相同的值和仅与一个属性相关的排序,我更喜欢使用 排序()方法;如果不是,请使用 OrderBy。

【讨论】:

根据当前的 MSDN 文档,Sort 根据输入使用 3 种不同的排序算法。其中包括快速排序。关于 OrderBy() 算法的问题在这里(快速排序):***.com/questions/2792074/…【参考方案6】:

为什么不测量它:

class Program

    class NameComparer : IComparer<string>
    
        public int Compare(string x, string y)
        
            return string.Compare(x, y, true);
        
    

    class Person
    
        public Person(string id, string name)
        
            Id = id;
            Name = name;
        
        public string Id  get; set; 
        public string Name  get; set; 
    

    static void Main()
    
        List<Person> persons = new List<Person>();
        persons.Add(new Person("P005", "Janson"));
        persons.Add(new Person("P002", "Aravind"));
        persons.Add(new Person("P007", "Kazhal"));

        Sort(persons);
        OrderBy(persons);

        const int COUNT = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        
            Sort(persons);
        
        watch.Stop();
        Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        
            OrderBy(persons);
        
        watch.Stop();
        Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);
    

    static void Sort(List<Person> list)
    
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    

    static void OrderBy(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    

在我的计算机上,当以发布模式编译时,此程序会打印:

Sort: 1162ms
OrderBy: 1269ms

更新:

正如@Stefan 所建议的,这里是对大列表进行较少次数排序的结果:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)

    persons.Add(new Person("P" + i.ToString(), "Janson" + i.ToString()));


Sort(persons);
OrderBy(persons);

const int COUNT = 30;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)

    Sort(persons);

watch.Stop();
Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)

    OrderBy(persons);

watch.Stop();
Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);

打印:

Sort: 8965ms
OrderBy: 8460ms

在这种情况下,OrderBy 的表现似乎更好。


更新2:

并使用随机名称:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)

    persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));

地点:

private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)

    var sb = new StringBuilder(size);
    int start = (lowerCase) ? 97 : 65;
    for (int i = 0; i < size; i++)
    
        sb.Append((char)(26 * randomSeed.NextDouble() + start));
    
    return sb.ToString();

产量:

Sort: 8968ms
OrderBy: 8728ms

OrderBy 还是更快

【讨论】:

我认为,对一个非常小的列表(3 个项目)进行 1000000 次排序,或者对一个非常大的列表(1000000 个项目)进行几次排序是有很大不同的。两者都非常相关。在实践中,中等大小的列表(什么是中等?......现在假设 1000 个项目)是最有趣的。恕我直言,用 3 个项目对列表进行排序不是很有意义。 请注意,“更快”和“明显更快”之间存在差异。在您的上一个示例中,差异约为四分之一秒。用户会注意到吗?用户等待将近 9 秒才能得到结果是不能接受的吗?如果这两个问题的答案都是“否”,那么从性能的角度来看,您选择哪一个并不重要。 还要注意,这里的测试在开始秒表之前对列表进行排序,所以我们比较两种算法在面对排序输入时的比较。这可能与它们在未排序输入下的相对性能完全不同。 这些结果非常令人惊讶,恕我直言,考虑到LINQ 与就地List&lt;T&gt;.Sort 实现相比必须花费额外的内存。我不确定他们是否在较新的 .NET 版本中改进了这一点,但在我的机器上(i7 第三代 64 位 .NET 4.5 版本)Sort 在所有情况下都优于OrderBy。此外,通过查看OrderedEnumerable&lt;T&gt; 源代码,它似乎在最终调用 Quicksort 之前创建了 三个 额外的数组(首先是 Buffer&lt;T&gt;,然后是投影键数组,然后是索引数组)对索引数组进行排序。 ...在这一切之后,有 ToArray 调用创建了结果数组。内存操作和数组索引是非常快的操作,但我仍然找不到这些结果背后的逻辑。【参考方案7】:

不,它们不是同一个算法。对于初学者,LINQ OrderBy 被记录为stable(即,如果两个项目具有相同的Name,它们将按其原始顺序出现)。

这还取决于您是缓冲查询还是对其进行多次迭代(LINQ-to-Objects,除非您缓冲结果,否则将按照 foreach 重新排序)。

对于OrderBy 查询,我也很想使用:

OrderBy(n => n.Name, StringComparer.yourchoiceIgnoreCase);

(对于yourchoiceCurrentCultureOrdinalInvariantCulture 之一)。

List&lt;T&gt;.Sort

此方法使用 Array.Sort,它 使用快速排序算法。这 实现执行不稳定 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。

Enumerable.OrderBy

此方法执行稳定排序;也就是说,如果两个元素的键相等,则保留元素的顺序。相反,不稳定的排序不会保留具有相同键的元素的顺序。 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。

【讨论】:

如果您使用 .NET Reflector 或 ILSpy 破解 Enumerable.OrderBy 并深入了解其内部实现,您可以看到 OrderBy 排序算法是 QuickSort 的一种变体,可以进行稳定排序。 (参见System.Linq.EnumerableSorter&lt;TElement&gt;。)因此,Array.SortEnumerable.OrderBy 都可以预期有 O(N log N) 执行时间,其中 N 是数字集合中的元素。 @Marc 我不太明白如果两个元素相等并且不保留它们的顺序会有什么区别。对于原始数据类型来说,这当然不是问题。但是,即使对于引用类型,如果我要排序,名字 Marc Gravell 的人出现在另一个名字为 Marc Gravell 的人之前(例如 :))又有什么关系呢?我不是在质疑你的答案/知识,而是在寻找这种场景的应用。 @Mukus 假设您按名称(或实际上按出生日期)对公司通讯录进行排序 - 不可避免地会有重复。最终的问题是:他们会发生什么?子订单定义了吗?

以上是关于C# Sort 和 OrderBy 比较的主要内容,如果未能解决你的问题,请参考以下文章

hive order by sort by distribute by和sort by一起使用 cluster by

Hive中sort by,order by,cluster by,distribute by总结

hive中order by,sort by, distribute by, cluster by的用法

hive中order by,sort by, distribute by, cluster by作用以及用法

hive 中 Order by, Sort by ,Dristribute by,Cluster By 的作用和用法

SQLi-LABS Page-3 (order by injections) Less-46-Less-53