.NET 排序 Array.SortT 实现分析

fa634cc1dc444110911412b08fc5e599.png

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> Memberspublic 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 pointSwapIfGreaterWithValues(keys, values, 0, hi);   // swap the low with the highSwapIfGreaterWithValues(keys, values, middle, hi); // swap the middle with the highTKey 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的排序实现,希望对您有用。

f1ee64f9139bdd1a0ec2e38016458fc6.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/297497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

联想电脑如何添加无线网络连接服务器,安装英特尔MYWIFI的操作步骤

适用范围:(1)操作系统&#xff1a;仅支持VISTA /WINDOWS 7&#xff0c;不支持WINDOWS XP/2003/2000&#xff1b;(2)硬件&#xff1a;INTEL MY WIFI支持INTEL 5100以及以上无线网卡&#xff0c;非INTEL无线网卡不支持。知识点分析:英特尔的MY WIFI技术是一项针对笔记本电脑无线网…

假如把女生比作一种水果

1 和睡相不好的人一起睡觉是什么体验&#xff1f;2 箱子里的是我方输出&#xff0c;外面的是对方打野3 女儿问爸爸小时候都玩什么&#xff0c;于是爸爸给她做了这个。。4 推上一网友随手拍到的照片&#xff0c;就好像是三张图片拼起来的一样。5 一位台湾艺用解剖学老师的硬核授…

ai怎么调界面大小_科研论文作图系列-从PPT到AI (一)

这是“投必得学术”推送的第44篇文章&#xff0c;专注科研技能和资讯分享&#xff01;关注“投必得学术”&#xff0c;可以看到我们所有干货和资讯&#xff01;导语&#xff1a;之前的推送中&#xff0c;小编给大家介绍过几款科研作图软件&#xff0c;包括统计分析软件Origin和…

讲师征集| .NET Conf China 2021正式启动!

去年年初疫情突袭武汉&#xff0c;打得我们措手不及在众多 .NET 开发者们的殷切期盼声中一场轰动极客圈的技术狂欢趴毅然在苏州盛大开启、圆满落幕&#xff01;我们坚信&#xff0c;你还记忆犹新……▽因为&#xff0c;TA 是 .NET 5.0 发布的”里程碑“线上线下轮番轰炸的技术干…

【Android游戏开发十一】手把手让你爱上Android sdk自带“9妹”

本站文章均为 李华明Himi 原创,转载务必在明显处注明&#xff1a; 转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/android-game/321.html 前几天群成员讨论过关于9patch的工具【我比较喜欢喊它9妹子&#xff0c;西西(*^_^*)】、然后研究了一下&#xff0c;比较…

为什么PostgreSQL比MongoDB还快之完结篇(深挖单点索引查询)

之前两篇测试中发现&#xff1a;单点索引查询中PostgreSQL的速度是MongoDB(WiredTiger引擎)的4倍。http://blog.chinaunix.net/xmlrpc.php?rblog/article&uid20726500&id4960138http://blog.chinaunix.net/xmlrpc.php?rblog/article&uid20726500&id4981629虽…

ajax和spa的区别,在XX团上消费过一次不正规的Spa,现在过来两个月公安局打电话叫我过去...

咨询我帮助人数&#xff1a;36021721.公安局打电话来的原因比较多&#xff0c;具体需要根据电话的内容进行分析。而且公安局的范围比较大&#xff0c;涉及到的部门比较多&#xff0c;每个部门负责处理各自的工作问题&#xff0c;有民事的&#xff0c;也有刑事的。有派出所的&am…

centos7 iptables 端口转发 保存_iptables 防火墙

目录&#xff1a;yum 在线安装yum卸载安装包rpm 卸载yum安装rpm离线安装利用 Downloadonly 插件下载 RPM 软件包及其所有依赖包使用 Yumdownloader 工具来下载 RPM 软件包及其所有依赖包yum 在线安装CentOS7默认的防火墙不是iptables,而是firewalle.yum卸载安装包yum remove to…

日本惊现神操作!偷偷研究飞刀方程致使厕所爆炸......

欢快如厕为何大声惨叫前几天&#xff0c;竟然有模友私信超模君&#xff0c;说这是不是真的。网友私信截图WTF&#xff01;数学史上就有一道“奇葩的”难题&#xff0c;是历代数学家们在厕所里解决的。厕所冥想1917年&#xff0c;为了给日本武士增添生活趣味&#xff0c;数学家挂…

抓取手机https_python爬虫入门02:教你通过 Fiddler 进行手机抓包

哟~哟~哟~hi起来everybody今天要说说怎么在我们的手机抓包通过python爬虫入门01&#xff1a;教你在 Chrome 浏览器轻松抓包我们知道了 HTTP 的请求方式以及在 Chrome 中摸清了一些套路但是除了对数据进行解析之外有时候我们想对请求的数据或者响应的数据进行篡改怎么做呢&#…

BeetleX进程服务管理组件应用

有些时候需要在程序中启动和管理其他应用进程&#xff0c;当碰到这样的需求的时候可以通过Process对象来完成&#xff1b;为了让使用和管理更方便在这基础上封装 了BeetleX.ServicesProcess组件&#xff0c;通过组件的管理中心让进程操作更方便&#xff0c;同时还集成了Web套件…

升级总代分享思路_桃生企业至尊七郎瓷砖新展厅全新升级惊艳亮相

桃生企业至尊七郎瓷砖新展厅惊艳亮相&#xff0c;将艺术和时尚完美融合&#xff0c;即将成为晋江天工建材城的新地标&#xff01;2020年桃生企业逆流升级进行中&#xff0c;全新展厅即将揭幕&#xff01;全新空间 对话年轻轻奢于行&#xff0c;优雅于里&#xff0c;全新一楼的…

烧脑又过瘾!关于c²= b² + a²,你不知道的N个事实

全世界只有3.14 % 的人关注了爆炸吧知识勾股定理你真的懂吗一般人看来&#xff0c;勾股定理只存在于特定的三角形或几何图形中。但实际上&#xff0c;绝大多数人都小看了这条有2600年历史的公式&#xff0c;很多看似不可能的图形&#xff0c;只要涉及到了平方数&#xff0c;勾股…

[snmp++]读取cisco路由交换机信息[一] - 环境搭建

首先从网上下载gn3这个摸拟器以及cisco路由器的bin. 如图&#xff0c;下面的clouds里添加本地连接&#xff0c;这样表示路由器与本机的网卡桥接。即在R2里的接口设置一个与本机的IP地址同一网段。这样局域网的机就可以与R2通信 R2的配置如下 snmp-server community public RO 读…

Dapr + .NET 实战(六)绑定

什么是绑定处理外部事件或调用外部接口的功能就是绑定&#xff0c;绑定可以提供以下好处&#xff1a;避免连接到消息系统 ( 如队列和消息总线 ) 并进行轮询的复杂性聚焦于业务逻辑&#xff0c;而不是如何与系统交互使代码不受 SDK 或外部库的强耦合处理重试和故障恢复Dapr提供了…

对象中multipartfile 空报错_Python 为什么会有个奇怪的“...”对象?

我想到一种特别的写法&#xff0c;很多人会把它当成 pass 语句的替代。在文章发布后&#xff0c;果然有三条留言提及了它。所谓特别的写法就是下面这个&#xff1a;# 用 ... 替代 passdef foo():...它是中文标点符号的半个省略号&#xff0c;也即由英文的 3 个点组成。如果你是…

XP硬盘安装Fedora14图文教程

XP硬盘安装Fedora14图文教程发表于&#xff1a;2010年11月03日 分类&#xff1a;Fedora 26 条评论 5,044 次阅读 昨天是Fedora 14的发布&#xff0c;这篇文章介绍的是如何在windows XP下硬盘安装Fedora 14。 之前写过2篇文章&#xff1a; Linux下硬盘安装Fedora 13&#xff1a;…

用wamper打开php源码_php源码该怎么设置数据库

php源码该怎么设置数据库php源码修改数据库的设置方法&#xff1a;首先在源码中找到程序的配置文件&#xff1b;然后用文本编辑器打开&#xff1b;之后修改数据库地址、数据库名、用户名、连接密码、数据库前缀等参数&#xff1b;最后保存即可。这里以织梦程序为例&#xff0c;…

豆瓣评分9.0+,这几部纪录片看一部少一部!

纪录片是以真实生活为创作素材&#xff0c;以真人真事为表现对象&#xff0c;并对其进行艺术的加工与展现的&#xff0c;以展现真实为本质&#xff0c;并用真实引发人们思考的电影或电视艺术形式。好的纪录片就像打开了一扇新世界的大门&#xff0c;让我们了解更多世界的奇妙之…

记一次 .NET 某纺织工厂 MES系统 API 挂死分析

一&#xff1a;背景 1. 讲故事这个月中旬&#xff0c;有位朋友加我wx求助他的程序线程占有率很高&#xff0c;寻求如何解决&#xff0c;截图如下&#xff1a;说实话&#xff0c;和不同行业的程序员聊天还是蛮有意思的&#xff0c;广交朋友&#xff0c;也能扩大自己的圈子&#…