.NET 排序 Array.Sort<T> 实现分析
System.Array.Sort<T>
是.NET内置的排序方法, 灵活且高效, 大家都学过一些排序算法,比如冒泡排序,插入排序,堆排序等,不过你知道这个方法背后使用了什么排序算法吗?
先说结果, 实际上 Array.Sort 不止使用了一种排序算法, 为了保证不同的数据量的排序场景,都能有一个高性能的表现,实现中包括了插入排序,堆排序和快速排序, 接下来从通过源码看看它都做了哪些事情。
Array.Sort
https://source.dot.net/#System.Private.CoreLib/Array.cs,ec5718fae85b7640
public static void Sort<T>(T[] array)
{
if (array == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
if (array.Length > 1)
{
var span = new Span<T>(ref MemoryMarshal.GetArrayDataReference(array), array.Length);
ArraySortHelper<T>.Default.Sort(span, null);
}
}
这里我们对 int 数组进行排序, 先看一下这个Sort方法, 当数组的长度大于1时, 会先把数组转成 Span 列表, 然后调用了内部的ArraySortHelper的Default对象的Sort方法。
ArraySortHelper
[TypeDependency("System.Collections.Generic.GenericArraySortHelper`1")]
internal sealed partial class ArraySortHelper<T>
: IArraySortHelper<T>
{
private static readonly IArraySortHelper<T> s_defaultArraySortHelper = CreateArraySortHelper();
public static IArraySortHelper<T> Default => s_defaultArraySortHelper;
[DynamicDependency("#ctor", typeof(GenericArraySortHelper<>))]
private static IArraySortHelper<T> CreateArraySortHelper()
{
IArraySortHelper<T> defaultArraySortHelper;
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
defaultArraySortHelper = (IArraySortHelper<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericArraySortHelper<string>), (RuntimeType)typeof(T));
}
else
{
defaultArraySortHelper = new ArraySortHelper<T>();
}
return defaultArraySortHelper;
}
}
Default 会根据是否实现了 IComparable<T>
接口来创建不同的 ArraySortHelper, 因为上面我对int数组进行排序, 所以调用的是 GenericArraySortHelper 的Sort方法。
GenericArraySortHelper
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,280
internal sealed partial class GenericArraySortHelper<T>
where T : IComparable<T>
{
// Do not add a constructor to this class because ArraySortHelper<T>.CreateSortHelper will not execute it
#region IArraySortHelper<T> Members
public void Sort(Span<T> keys, IComparer<T>? comparer)
{
try
{
if (comparer == null || comparer == Comparer<T>.Default)
{
if (keys.Length > 1)
{
// For floating-point, do a pre-pass to move all NaNs to the beginning
// so that we can do an optimized comparison as part of the actual sort
// on the remainder of the values.
if (typeof(T) == typeof(double) ||
typeof(T) == typeof(float) ||
typeof(T) == typeof(Half))
{
int nanLeft = SortUtils.MoveNansToFront(keys, default(Span<byte>));
if (nanLeft == keys.Length)
{
return;
}
keys = keys.Slice(nanLeft);
}
IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
}
}
else
{
ArraySortHelper<T>.IntrospectiveSort(keys, comparer.Compare);
}
}
catch (IndexOutOfRangeException)
{
ThrowHelper.ThrowArgumentException_BadComparer(comparer);
}
catch (Exception e)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_IComparerFailed, e);
}
}
首先会判断排序的类型是否是浮点型, 如果是的会做一些排序的调整优化,然后调用了 IntroSort 方法,并传入了两个参数,第一个Keys就是数组的Span列表,那第二个是什么呢? 它是一个int类型的depthLimit参数,这里简单点理解就是算出数组的深度,因为后边会根据这个值进行递归操作,然后进入到 IntroSort 方法。
IntroSort
到这个方法这里就清晰很多了, 这是Array.Sort<T>
排序的主要内容,接着往下看
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,404
private static void IntroSort(Span<T> keys, int depthLimit)
{
Debug.Assert(!keys.IsEmpty);
Debug.Assert(depthLimit >= 0);
int partitionSize = keys.Length;
while (partitionSize > 1)
{
if (partitionSize <= Array.IntrosortSizeThreshold)
{
if (partitionSize == 2)
{
SwapIfGreater(ref keys[0], ref keys[1]);
return;
}
if (partitionSize == 3)
{
ref T hiRef = ref keys[2];
ref T him1Ref = ref keys[1];
ref T loRef = ref keys[0];
SwapIfGreater(ref loRef, ref him1Ref);
SwapIfGreater(ref loRef, ref hiRef);
SwapIfGreater(ref him1Ref, ref hiRef);
return;
}
InsertionSort(keys.Slice(0, partitionSize));
return;
}
if (depthLimit == 0)
{
HeapSort(keys.Slice(0, partitionSize));
return;
}
depthLimit--;
int p = PickPivotAndPartition(keys.Slice(0, partitionSize));
// Note we've already partitioned around the pivot and do not have to move the pivot again.
IntroSort(keys[(p+1)..partitionSize], depthLimit);
partitionSize = p;
}
}
第一次进入方法时,partitionSize 就是数组的长度, 这里有一个判断条件,如下, IntrosortSizeThreshold 是一个值为16的常量,它是一个阈值, 如果数组的长度小于等于16, 那么使用的就是插入排序(InsertionSort), 为什么是16呢?这里通过注释了解到, 从经验上来看, 16及以下得数组长度使用插入排序的效率是比较高的。
if (partitionSize <= Array.IntrosortSizeThreshold)
{
if (partitionSize == 2)
{
SwapIfGreater(ref keys[0], ref keys[1]);
return;
}
if (partitionSize == 3)
{
ref T hiRef = ref keys[2];
ref T him1Ref = ref keys[1];
ref T loRef = ref keys[0];
SwapIfGreater(ref loRef, ref him1Ref);
SwapIfGreater(ref loRef, ref hiRef);
SwapIfGreater(ref him1Ref, ref hiRef);
return;
}
InsertionSort(keys.Slice(0, partitionSize));
return;
}
InsertionSort
如果数组的长度小于等于3时, 直接进行对比交换, 如果长度大约3并且小于等于16的话, 使用插入排序(InsertionSort), 方法内容如下:
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,537
private static void InsertionSort(Span<T> keys)
{
for (int i = 0; i < keys.Length - 1; i++)
{
T t = Unsafe.Add(ref MemoryMarshal.GetReference(keys), i + 1);
int j = i;
while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j))))
{
Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j);
j--;
}
Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = t!;
}
}
HeapSort
if (depthLimit == 0)
{
HeapSort(keys.Slice(0, partitionSize));
return;
}
depthLimit--;
因为后边是递归操作,所以每次 depthLimit 都会减1, 当深度为0排序还没有完成的时候,就会直接使用堆排序(HeapSort),方法内容如下:
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,990
private static void HeapSort(Span<TKey> keys, Span<TValue> values)
{
Debug.Assert(!keys.IsEmpty);
int n = keys.Length;
for (int i = n >> 1; i >= 1; i--)
{
DownHeap(keys, values, i, n);
}
for (int i = n; i > 1; i--)
{
Swap(keys, values, 0, i - 1);
DownHeap(keys, values, 1, i - 1);
}
}
private static void DownHeap(Span<TKey> keys, Span<TValue> values, int i, int n)
{
TKey d = keys[i - 1];
TValue dValue = values[i - 1];
while (i <= n >> 1)
{
int child = 2 * i;
if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child])))
{
child++;
}
if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))
break;
keys[i - 1] = keys[child - 1];
values[i - 1] = values[child - 1];
i = child;
}
keys[i - 1] = d;
values[i - 1] = dValue;
}
QuickSort
int p = PickPivotAndPartition(keys.Slice(0, partitionSize), values.Slice(0, partitionSize));
IntroSort(keys[(p+1)..partitionSize], values[(p+1)..partitionSize], depthLimit);
partitionSize = p;
这里调用了另外一个方法 PickPivotAndPartition
,
Pivot 基准, Partition 分区, 这就是快速排序呀!而且还是使用了尾递归的快速排序,其中也使用了三数取中法,方法内容如下
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,945
private static int PickPivotAndPartition(Span<TKey> keys, Span<TValue> values)
{
Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);
int hi = keys.Length - 1;
// Compute median-of-three. But also partition them, since we've done the comparison.
int middle = hi >> 1;
// Sort lo, mid and hi appropriately, then pick mid as the pivot.
SwapIfGreaterWithValues(keys, values, 0, middle); // swap the low with the mid point
SwapIfGreaterWithValues(keys, values, 0, hi); // swap the low with the high
SwapIfGreaterWithValues(keys, values, middle, hi); // swap the middle with the high
TKey pivot = keys[middle];
Swap(keys, values, middle, hi - 1);
int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
while (left < right)
{
if (pivot == null)
{
while (left < (hi - 1) && keys[++left] == null) ;
while (right > 0 && keys[--right] != null) ;
}
else
{
while (GreaterThan(ref pivot, ref keys[++left])) ;
while (LessThan(ref pivot, ref keys[--right])) ;
}
if (left >= right)
break;
Swap(keys, values, left, right);
}
// Put pivot in the right location.
if (left != hi - 1)
{
Swap(keys, values, left, hi - 1);
}
return left;
}
总结
本文主要介绍了System.Array.Sort<T>
排序的内部实现, 发现它使用了插入排序,堆排序和快速排序,大家有兴趣可以看一下Java或者Golang的排序实现,希望对您有用。
.NET 排序 Array.Sort<T> 实现分析的更多相关文章
- C# 字典排序Array.Sort
Array.Sort可以实现便捷的字典排序,但如果完全相信他,那么就容易产生些异常!太顺利了,往往是前面有坑等你. 比如:微信接口,好多地方需要签名认证,签名的时候需要用的字典排序,如果只用Array ...
- [ActionScript 3.0] 对数组中的元素进行排序Array.sort()的方法
对数组中的元素进行排序. 此方法按 Unicode 值排序. (ASCII 是 Unicode 的一个子集.) 默认情况下,Array.sort()按以下方式进行排序: 1. 排序区分大小写(Z优先于 ...
- FCL源码中数组类型的学习及排序函数Sort函数的分析
Array 是所有数组的基类ArrayList 解决了所有Array 类的缺点 能动态扩容, 但是类型不安全的,而是会有装箱与拆箱的性能开销List<T> 则是解决了ArrayLis ...
- 自带排序 Array.sort()
public static void main(String[] args) { int[] aa = {1, 5, 9, 7, 3, 1, 6, 3, 47}; Arrays.sort(aa); p ...
- 排序算法| Array.sort()算法规则
1.js的Array.sort()是使用什么算法排序: 1.火狐中是“归并排序” 2.V8引擎是 “插入排序和快速排序结合”.数组长度不超过10时,使用插入排序.长度超过10使用快速排序.在数组较短时 ...
- Python中排序方法sort、函数sorted的key参数的作用分析
从Python2.4开始,list.sort方法 和 sorted方法 都增加了一个 'key' 参数用来在进行比较之前指定每个列表元素上要调用的函数,将函数的返回值作为比较的依据. 那么怎么使用这个 ...
- 简单的Array.sort 排序方法
[排序]sort类 Arrays.sort升序排序 import java.util.Arrays;//导入Arrays类public class menu{ public static voi ...
- javascript array sort()
[5,10,1].sort(); 结果[1,10,5] 有点出人意料. array.sort( sortFunction )可选-指定如何比较元素顺序的函数名称 如果省略sortFunction参数, ...
- [python学习] 语言基础—排序函数(sort()、sorted()、argsort()函数)
python的内建排序函数有 sort.sorted两个. 1.基础的序列升序排序直接调用sorted()方法即可 ls = list([5, 2, 3, 1, 4]) new_ls = sorted ...
随机推荐
- 迭代器 与 foreach 的区别
迭代器的常见运用--Eg:有一组数据 需要对每个符合条件的数据 进行记录 static void Main() { int[] s = new int[] { 1, 2, 8 }; foreach ( ...
- rocketmq知识点
消息队列mq 参考资料:https://www.jianshu.com/p/824066d70da8 一.消息中间件的主要作用和功能: 1)异步解耦和分流: 2)挡住前端的数据洪峰,保证后端系统的稳定 ...
- 十三:Servlet3.0的异步
servlet之前的操作同时同步的,就是按照这样的一个流程来走的: 1.请求根据一个路径路由到一个servlet中, 2.servlet获取一系列的参数 3.执行一系列的逻辑(花费时间所占的比重也更大 ...
- Javascript - Vue - vuex
vuex 这是一个与vue配套的公共数据管理工具,可以将一些需要共享的数据保存到vuex中,以此方便项目中的任何组件都可以从vuex中得到共享数据.cnpm i vuex -S 装包 读取数据 //在 ...
- ArrayList线程不安全怎么办?(CopyOnWriteArrayList详解)
ArrayList线程不安全怎么办? 有三种解决方法: 使用对应的 Vector 类,这个类中的所有方法都加上了 synchronized 关键字 就和 HashMap 和 HashTable 的关系 ...
- centos7 Tomcat 多项目配置
2021-07-30 1. Tomcat 各目录功能说明 bin :脚本文件目录,存放启动和关闭 Tomcat 的脚本文件conf:存放 Tomcat 的配置文件,server.xml 尤其重要log ...
- MyBatis学习总结(三)——MyBatis配置文件配置的优化
一.连接数据库的配置单独放在一个properties文件中 上文 连接数据库的配置写在 mybatisConf.xml中,本文直接放在 db.properties 中, 在mybatisConf.xm ...
- Java 字符串格式化和工具类使用
前言 我们在做项目时候经常需要对字符串进行处理,判断,操作,所以我就总结了一下java 字符串一些常用操作,和推荐比较好用我在自用的工具类,毕竟有轮子我们自己就不用重复去写了,提供开发效率,剩下的时间 ...
- [第六篇]——云服务器之Spring Cloud直播商城 b2b2c电子商务技术总结
云服务器 云服务器(Elastic Compute Service, ECS)是一种简单高效.安全可靠.处理能力可弹性伸缩的计算服务. 云服务器管理方式比物理服务器更简单高效,我们无需提前购买昂贵的硬 ...
- Elasticsearch的基本使用
1. 概述 之前聊了一下 Elasticsearch 的安装,今天我们来说说 Elasticsearch 的基本使用. 2. Elasticsearch索引的使用 索引(index)相当于是mysql ...