.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,一经查实,立即删除!

相关文章

Android 中文api (81)——InputMethod [输入法]

前言 本章内容是android.view.inputmethod.InputMethod&#xff0c;为输入法相关章节&#xff0c;版本为Android 2.3 r1&#xff0c;翻译来自"六必治"&#xff0c;欢迎大家访问他的博客&#xff1a;http://www.cnblogs.com/zcmky/&#xff0c;再次感谢"六必治&q…

联想电脑如何添加无线网络连接服务器,安装英特尔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 一位台湾艺用解剖学老师的硬核授…

张萍萍 计科高职13-1 201303014010

通过这次的实践&#xff0c;我第一次感觉学好一门英语是多么的重要&#xff0c;这次历尽千辛万险才把作业完成&#xff0c;通过这次实践我发现我还有许多的地方进行改进&#xff0c;不过通过这次试验我也学到了不少的东西&#xff0c;我学会了如何使用gethub来管理代码和如何管…

python tkinter进度条_在python3.7中更新tkinter进度条

抱歉&#xff0c;花了一段时间&#xff0c;但我能搞定。在 我不知道你遇到了什么与Python3.x不兼容的地方&#xff0c;但我找到了我跟踪的这个更新的视频。在 除了“停止”命令之外&#xff0c;它几乎完美地工作了&#xff0c;我无法开始工作。在from tkinter import * from tk…

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

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

HDU3363_贪心

解题大意&#xff1a; 给你一个串&#xff0c;串中有H跟T两种字符&#xff0c;然后切任意刀&#xff0c;使得能把H跟T各自分为原来的一半。 解题思路&#xff1a; 把串想象成一个环&#xff0c;只要满足H跟T都为偶数个&#xff0c;那么就可以做一条过圆心的直线把H跟T平分掉&am…

讲师征集| .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;数学家挂…

(转)学习密度与专注力

转自&#xff1a;http://mindhacks.cn/2007/05/24/learn-to-focus/作者&#xff1a;刘未鹏上次学校里面有一个免费的李阳英语讲座&#xff0c;好奇于是就去听了一下。对一句话印象比较深刻&#xff0c;大意是说许多人学了快10年的英语&#xff0c;其开口的时间还不如在集训的七…

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

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

BeetleX进程服务管理组件应用

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

server 2008 服务器不能访问 java项目,Java 8上的SQL Server JDBC错误:驱动程序无法使用安全套接字层(SSL)加密建立到SQL Server的安全连接...

我在一个Linux实例上的Java 8 JVM中启用了SSL记录,从而再现了这个问题.使用-Djavax.net.debug ssl&#xff1a;handshake&#xff1a;verbose打开SSL日志记录.这显示了一些有用的信息.我们在生产中使用并已证明可以为我们工作的解决方法是在JVM上设置此参数&#xff1a;-Djdk.…

自动上传下载

#!/bin/bashftp -n<<!open 192.168.1.171user guest 1234561. -n 不受.netrc文件的影响。&#xff08;ftp默认为读取.netrc文件中的设定&#xff09;2. << 是使用即时文件重定向输入。3. !是即时文件的标志它必须成对出现&#xff0c;以标识即时文件的开始和结尾#!…

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

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

常胜游戏系列:填满硬币

题目&#xff1a; 有个桌子&#xff0c;两个人交替往桌子上放硬币&#xff0c;当桌子被填满时&#xff0c;最后一个放硬币者获胜。求常胜策略。 方法&#xff1a; 每次都第一个先放&#xff0c;放在桌子中心&#xff0c;之后始终放在对方的对称位置。 转载于:https://blog.51ct…