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);
}
}
}