Array.Sort有N个重载,都是调用同一个方法实现排序(Array.Sort<T>先不管)
public static void Sort(Array keys, Array items, int index, int length, IComparer comparer) { // 省略一堆参数检查代码 if ((length > 1) && (((comparer != Comparer.Default) && (comparer != null)) || !TrySZSort(keys, items, index, (index + length) - 1))) { object[] objArray = keys as object[]; object[] objArray2 = null; if (objArray != null) { objArray2 = items as object[]; } if ((objArray != null) && ((items == null) || (objArray2 != null))) { new SorterObjectArray(objArray, objArray2, comparer).QuickSort(index, (index + length) - 1); } else { new SorterGenericArray(keys, items, comparer).QuickSort(index, (index + length) - 1); } } }
其中跟排序有关的代码有三处SorterObjectArray和SorterGenericArray都是Array的内部类,还有个是TrySZSort方法。
TrySZSort方法从签名来看应该是调用外部非托管的代码。
[MethodImpl(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)] private static extern bool TrySZSort(Array keys, Array items, int left, int right);
也就是说,当没有自定义的Comparer对象时,Array优先尝试调用非托管代码对数组进行排序。
接下来看看SorterGenericArray和SorterObjectArray的唯一区别:
SorterObjectArray中直接使用Array.GetValue()/Array.SetValue()对数组进行读写;
SorterGenericArray中则通过Array[]索引器对数组进行读写。
Array[]索引器是怎样的呢,先看看Array里索引器的实现代码:
object IList.this[int index] { get { return this.GetValue(index); } set { this.SetValue(value, index); } }
Array索引器都是通过Array.GetValue()/Array.SetValue()对数组进行读写的,为什么SorterGenericArray还多此一举呢?
应当注意的是,这里的索引器是显式IList接口的索引器的实现方法,并不是Array[]索引器的实现方法,对于泛型,情况则不一样:
IList<T>泛型接口的定义如下:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { // ... T this[int index] { get; set; } }
IList<T>并不实现IList接口,而是自定义了一个强类型的索引器。
再来看看List<T>泛型类的定义,List实现了List和IList<T>两种接口:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
List<T>索引器的实现代码:
object IList.this[int index] { get { return this[index]; } set { List<T>.VerifyValueType(value); this[index] = (T) value; } } public T this[int index] { get { if (index >= this._size) { ThrowHelper.ThrowArgumentOutOfRangeException(); } return this._items[index]; } set { if (index >= this._size) { ThrowHelper.ThrowArgumentOutOfRangeException(); } this._items[index] = value; this._version++; } }
注意List<T>的代码在实现IList的索引器时是用显示接口声明的,但IList<T>则没有显式写明接口,于是:
List<int> list = new List<int>(10); list[0]; //这里调用的是IList<int>.this[int]索引器,返回的是int类型 ((IList)list)[0]; //这里调用的是IList.this[int]索引器,返回的是object类型
值得注意的是Array.Sort()接受的是一个Array对象
List<int> myList = new List<int>(); Array.Sort(myList); // 编译错误:参数类型不匹配 Array.Sort<int>(myList); // 编译错误:注意Array.Sort<T>接受参数的是T[]数组
其实对于泛型,使用myList.Sort()就可以了(List<T>.Sort<T>实际还是调用了Array.Sort<T>方法,把内部管理的强类型数组传了过去)
现在回来看Array.Sort方法,既然Array只接受Array对象,为什么还要弄一个SorterGenericArray出来呢?
我们可以注意到只有当keys或者items(item为null时除外)不能转换成一个数组时(items),才会使用SorterGenericArray.QuickSort进行排序
有时候会给泛型类型增加一个显示/隐式类型转换,把一个泛型类型转化成一个Array数组(并不是调用ToArray哟),在这种情况下,还是可以调用Array.Sort方法的,如果这时候把这种数组交给SorterObjectArray排序,就可能有两个问题:一是泛型的强类型无法保证,二是IComparer.CompareTo调用时也会有类型问题,所以这里引入了SorterGenericArray类专门处理这类情况,毫无疑问,多了一重类型检查,排序效率相对会低点,可以在下面测试中看出来。于是有了下面的对比测试:
A.把SorterGenericArray代码提取出来,去除所有try等异常检查,作为MySorterWithComparer类主要代码进行排序
B.将A的所有GetValue/SetValue直接使用强类型索引器,并使用<和>操作符代替所有Comparer,作为MySorterWithIntArray类主要代码进行排序
C.Array.Sort()
D.Array.Sort<int>()
E.List<int>.Sort<int>()排序
对长度分别为100/500/1000/10000/10000的int型数组进行排序,发现以下情况:
1. 无论任何情况,使用Comparer和Array.GetValue()/Array.SetValue()效率都很低
2. Array.Sort()/Array.Sort<int>()/List<int>.Sort<int>()性能十分接近
3. 数组长度较小的情况下直接调用Array.Sort的效率比MySorterWithIntArray代码高很多
4. 数组长度超过1000后,MySorterWithIntArray排序比直接调用Array.Sort()效率高一点点
上面现象说明:
1. SorterGenericArray.QuickSort的实现是比较低效的
2. 或许是因为Array是定义在mscorlib中的,MySorterWithIntArray的代码则是在自己生成的程序集中,排序时使用了mscorlib程序集的类和方法。在数组长度较小的情况下,可能由于跨程序集调用的开销比实际排序的开销要大,所以没有直接调用Array.Sort高效。另外一个可能的原因是Array.Sort调用了非托管的代码。
3. 数组长度达到一定规模后,跨程序集调用的开销跟实际排序的开销比已经微乎其微,这时频繁的异常检查(Array.Sort内部有个try...catch)的开销逐渐显现。
其实就算数组比较大(>10w),MySorterWithIntArray花费的时间也只是比Array.Sort快了不到10%,总体上来说,使用Array.Sort还是一种高效方便的排序方法。
回想现在公司的笔试题,还考啥老掉牙的冒泡排序,事实就是.Net自带的快速排序函数已经非常高效,.Net程序员考排序这种事还是少来吧。
下面是测试记录(Release+代码优化版本,数据很接近,这里不重复了):
Array Size: 100
Measure Name: MySorterWithComparer.QuickSort
Time Elapsed: 3ms
CPU Cycles: 4,662,548
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: MySorterWithIntArray.QuickSort
Time Elapsed: 1ms
CPU Cycles: 1,856,063
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort
Time Elapsed: 0ms
CPU Cycles: 29,953
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort<int>
Time Elapsed: 0ms
CPU Cycles: 29,084
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: List.Sort
Time Elapsed: 0ms
CPU Cycles: 24,893
Gen 0: 0
Gen 1: 0
Gen 2: 0
Array Size: 500
Measure Name: MySorterWithComparer.QuickSort
Time Elapsed: 3ms
CPU Cycles: 6,358,979
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: MySorterWithIntArray.QuickSort
Time Elapsed: 0ms
CPU Cycles: 103,741
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort
Time Elapsed: 0ms
CPU Cycles: 119,614
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort<int>
Time Elapsed: 0ms
CPU Cycles: 122,639
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: List.Sort
Time Elapsed: 0ms
CPU Cycles: 118,514
Gen 0: 0
Gen 1: 0
Gen 2: 0
Array Size: 1000
Measure Name: MySorterWithComparer.QuickSort
Time Elapsed: 8ms
CPU Cycles: 14,291,893
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: MySorterWithIntArray.QuickSort
Time Elapsed: 0ms
CPU Cycles: 201,663
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort
Time Elapsed: 0ms
CPU Cycles: 251,647
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort<int>
Time Elapsed: 0ms
CPU Cycles: 248,611
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: List.Sort
Time Elapsed: 0ms
CPU Cycles: 236,148
Gen 0: 0
Gen 1: 0
Gen 2: 0
Array Size: 10000
Measure Name: MySorterWithComparer.QuickSort
Time Elapsed: 108ms
CPU Cycles: 180,057,306
Gen 0: 3
Gen 1: 0
Gen 2: 0
Measure Name: MySorterWithIntArray.QuickSort
Time Elapsed: 1ms
CPU Cycles: 2,456,267
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort
Time Elapsed: 1ms
CPU Cycles: 2,663,441
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort<int>
Time Elapsed: 1ms
CPU Cycles: 2,671,900
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: List.Sort
Time Elapsed: 1ms
CPU Cycles: 2,683,780
Gen 0: 0
Gen 1: 0
Gen 2: 0
Array Size: 100000
Measure Name: MySorterWithComparer.QuickSort
Time Elapsed: 1,224ms
CPU Cycles: 2,174,465,964
Gen 0: 39
Gen 1: 0
Gen 2: 0
Measure Name: MySorterWithIntArray.QuickSort
Time Elapsed: 15ms
CPU Cycles: 28,609,746
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort
Time Elapsed: 17ms
CPU Cycles: 30,748,663
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: Array.Sort<int>
Time Elapsed: 17ms
CPU Cycles: 30,883,336
Gen 0: 0
Gen 1: 0
Gen 2: 0
Measure Name: List.Sort
Time Elapsed: 17ms
CPU Cycles: 30,843,142
Gen 0: 0
Gen 1: 0
Gen 2: 0
测试代码:
namespace Just4Test.SortTest { class SortTestCode { static void Main(string[] args) { TestSort(100); TestSort(500); TestSort(1000); TestSort(10000); TestSort(100000); } static int[] GenerateArray(int count) { Random rnd = new Random(); int[] array = new int[count]; for (int i = 0; i < count; i++) { array[i] = rnd.Next(int.MinValue, int.MaxValue); } return array; } static void TestSort(int count) { Console.WriteLine("Array Size: " + count.ToString()); int[] src = GenerateArray(count); MySorterWithComparer arr1 = new MySorterWithComparer((int[])(src.Clone())); MySorterWithIntArray arr2 = new MySorterWithIntArray((int[])(src.Clone())); int[] arr3 = (int[])(src.Clone()); int[] arr4 = (int[])(src.Clone()); List<int> list = new List<int>((int[])(src.Clone())); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); CodeTimer.BeginMeasure("MySorterWithComparer.QuickSort"); arr1.QuickSort(0, count - 1); CodeTimer.EndMeasure(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); CodeTimer.BeginMeasure("MySorterWithIntArray.QuickSort"); arr2.QuickSort(0, count - 1); CodeTimer.EndMeasure(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); CodeTimer.BeginMeasure("Array.Sort"); Array.Sort(arr3); CodeTimer.EndMeasure(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); CodeTimer.BeginMeasure("Array.Sort<int>"); Array.Sort<int>(arr4); CodeTimer.EndMeasure(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); CodeTimer.BeginMeasure("List.Sort"); list.Sort(); CodeTimer.EndMeasure(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); } } class MySorterWithComparer { private Array array; private IComparer comparer; internal MySorterWithComparer(Array array) { this.array = array; this.comparer = Comparer.Default; } internal void SwapIfGreaterWithItems(int a, int b) { if (a != b) { if (this.comparer.Compare(this.array.GetValue(a), this.array.GetValue(b)) > 0) { object obj2 = this.array.GetValue(a); this.array.SetValue(this.array.GetValue(b), a); this.array.SetValue(obj2, b); } } } internal void QuickSort(int left, int right) { do { int low = left; int hi = right; int median = (low + ((hi - low) >> 1)); this.SwapIfGreaterWithItems(low, median); this.SwapIfGreaterWithItems(low, hi); this.SwapIfGreaterWithItems(median, hi); object y = this.array.GetValue(median); do { while (this.comparer.Compare(this.array.GetValue(low), y) < 0) { low++; } while (this.comparer.Compare(y, this.array.GetValue(hi)) < 0) { hi--; } if (low > hi) { break; } if (low < hi) { object obj3 = this.array.GetValue(low); this.array.SetValue(this.array.GetValue(hi), low); this.array.SetValue(obj3, hi); } if (low != 0x7fffffff) { low++; } if (hi != -2147483648) { hi--; } } while (low <= hi); if ((hi - left) <= (right - low)) { if (left < hi) { this.QuickSort(left, hi); } left = low; } else { if (low < right) { this.QuickSort(low, right); } right = hi; } } while (left < right); } } class MySorterWithIntArray { private int[] array; internal MySorterWithIntArray(int[] array) { this.array = array; } internal void SwapIfGreaterWithItems(int a, int b) { if (a != b && array[a] > array[b]) { int c = array[a]; array[a] = array[b]; array[b] = c; } } internal void QuickSort(int left, int right) { do { int low = left; int high = right; int median = (low + ((high - low) >> 1)); SwapIfGreaterWithItems(low, median); SwapIfGreaterWithItems(low, high); SwapIfGreaterWithItems(median, high); int y = array[median]; do { while (array[low] < y) low++; while (y < array[high]) high--; if (low > high) break; if (low < high) { int tmp = array[low]; array[low] = array[low]; array[high] = tmp; } low++; high--; } while (low <= high); if ((high - left) <= (right - low)) { if (left < high) QuickSort(left, high); left = low; } else { if (low < right) QuickSort(low, right); right = high; } } while (left < right); } } }