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 => x.Id
)。在排序之前首先提取所有键。与使用 Sort() 和自定义比较器相比,这可能会产生更好的性能。
来源: MDSN、reference source 和 dotnet/coreclr 存储库 (GitHub)。
上面列出的一些语句基于当前的 .NET 框架实现 (4.7.2)。将来可能会改变。
【讨论】:
【参考方案3】:我认为注意Sort
和OrderBy
之间的另一个区别很重要:
假设存在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 重载,该重载采用两个数组,一个是键,另一个是值.在填充键数组时,您将调用CalculateSalaryn
次。这显然不如使用 OrderBy 方便。【参考方案4】:
Darin Dimitrov 的回答表明,OrderBy
在面对已经排序的输入时比List.Sort
稍快。我修改了他的代码,让它反复对未排序的数据进行排序,OrderBy
在大多数情况下会稍微慢一些。
此外,OrderBy
测试使用ToArray
来强制枚举 Linq 枚举器,但这显然返回了与输入类型 (List<Person>
) 不同的类型 (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) 中运行测试代码,OrderByWithToList
与 OrderBy
花费的时间相同。【参考方案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<T>.Sort
实现相比必须花费额外的内存。我不确定他们是否在较新的 .NET 版本中改进了这一点,但在我的机器上(i7 第三代 64 位 .NET 4.5 版本)Sort
在所有情况下都优于OrderBy
。此外,通过查看OrderedEnumerable<T>
源代码,它似乎在最终调用 Quicksort 之前创建了 三个 额外的数组(首先是 Buffer<T>
,然后是投影键数组,然后是索引数组)对索引数组进行排序。
...在这一切之后,有 ToArray
调用创建了结果数组。内存操作和数组索引是非常快的操作,但我仍然找不到这些结果背后的逻辑。【参考方案7】:
不,它们不是同一个算法。对于初学者,LINQ OrderBy
被记录为stable(即,如果两个项目具有相同的Name
,它们将按其原始顺序出现)。
这还取决于您是缓冲查询还是对其进行多次迭代(LINQ-to-Objects,除非您缓冲结果,否则将按照 foreach
重新排序)。
对于OrderBy
查询,我也很想使用:
OrderBy(n => n.Name, StringComparer.yourchoiceIgnoreCase);
(对于yourchoice
CurrentCulture
、Ordinal
或InvariantCulture
之一)。
List<T>.Sort
此方法使用 Array.Sort,它 使用快速排序算法。这 实现执行不稳定 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。
Enumerable.OrderBy
此方法执行稳定排序;也就是说,如果两个元素的键相等,则保留元素的顺序。相反,不稳定的排序不会保留具有相同键的元素的顺序。 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。
【讨论】:
如果您使用 .NET Reflector 或 ILSpy 破解Enumerable.OrderBy
并深入了解其内部实现,您可以看到 OrderBy 排序算法是 QuickSort 的一种变体,可以进行稳定排序。 (参见System.Linq.EnumerableSorter<TElement>
。)因此,Array.Sort
和 Enumerable.OrderBy
都可以预期有 O(N log N) 执行时间,其中 N 是数字集合中的元素。
@Marc 我不太明白如果两个元素相等并且不保留它们的顺序会有什么区别。对于原始数据类型来说,这当然不是问题。但是,即使对于引用类型,如果我要排序,名字 Marc Gravell 的人出现在另一个名字为 Marc Gravell 的人之前(例如 :))又有什么关系呢?我不是在质疑你的答案/知识,而是在寻找这种场景的应用。
@Mukus 假设您按名称(或实际上按出生日期)对公司通讯录进行排序 - 不可避免地会有重复。最终的问题是:他们会发生什么?子订单定义了吗?以上是关于C# Sort 和 OrderBy 比较的主要内容,如果未能解决你的问题,请参考以下文章