浅析:java的“排序“函数使用了哪些“算法“

hello 兄弟们

我是浩说

前几天在做数据排序的时候

手滑点进了Arrays.sort()方法的源码里

本着"既来之,则安之"的心态

索性哥们儿就看了一番

没想到有了新收获

原来

Arrays.sort()方法会根据不同的情况使用不同的"排序算法"

接下来就给兄弟们详细汇报一下具体情况

关于Arrays.sort()

先给不熟悉的兄弟们科普一下

jdk提供的排序工具类主要有两个:

java.util.Arrays
java.util.Collections

大家可能更常用

Collections.sort() + 重写compare方法

的方式来基于某个属性排序

不过翻看Collections.sort()源码你会发现,

最终也是调用Arrays.sort()方法进行排序:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

所以Arrays.sort()才是排序的最终处理入口,了解一下还是很有必要的!

常用的排序算法

我们简单罗列一下目前常用的排序算法英文说明,以便后面阅读源码做参考:

  • 冒泡排序 Bubble Sort
  • 插入排序 Insertion Sort
  • 归并排序 Merge Sort
  • 快排 Quick sort

源码浅析

纵览Arrays.sort()所有的重载方法

我们可以从"被排序对象的数据类型"角度来分别推敲具体使用的排序算法

 
1 | 基本数据类型

拿int类型举例

(其它基本数据类型逻辑相同)

01 -> Arrays.sort()

方法里只有一行代码,看英文我们对照刚才罗列的算法不难猜到,

这大致是一个快排算法,我们点进去看一下:

public static void sort(int[] a) {
    //快排  Quick sort
    DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}

02 -> DualFivotQuicksort.sort()

该方法存在两个分支,且由数组的长度决定走向

根据两处注释来看

我们对照刚才罗列的算法可以暂时得出一个结论:

数组长度大于286,使用归并排序

小于286则使用快排

static void sort(int[] a, int left, int right,
                 int[] work, int workBase, int workLen) {
    // Use Quicksort on small arrays
    if (right - left < QUICKSORT_THRESHOLD) {
        sort(a, left, right, true);
        return;
    }
    ...
    // Merging
    ...
}
//QUICKSORT_THRESHOLD
private static final int QUICKSORT_THRESHOLD = 286;

但事实真的如此吗?

我们进入快排的sort方法

03 -> sort(a, left, right, true)

该方法里又存在着两个分支

private static void sort(int[] a, int left, int right, boolean leftmost) {
    int length = right - left + 1;
    // Use insertion sort on tiny arrays
    if (length < INSERTION_SORT_THRESHOLD) {
        ...
    }
    ...
}
//INSERTION_SORT_THRESHOLD
private static final int INSERTION_SORT_THRESHOLD = 47;

根据注释说明,再对照我们上面罗列的算法英文说明

我们修正一下刚才的结论:

当数组长度小于47,使用插入排序

大于47且小于286才真正使用快排

所以其实快排方法并不只是快排

结论总结

对于基本数据类型排序

具体排序算法取决于元素个数

< 47 插入排序

> 47 且 < 286 快排

> 286 归并排序

 
2 | 泛型

这种应该就是我们常用的,

也就是通过Collections.sort()调用

让我们逐步分析一下:

01 -> Collections.sort()

两个重载方法://入参List<T> list

public static <T extends Comparable<? super T>> void sort(List<T> list) {

    list.sort(null);

}




/**

 * 入参

 * List<T> list

 * 带比较器 Comparator<? super T> c

 */

public static <T> void sort(List<T> list, Comparator<? super T> c) {

    list.sort(c);

}

02 -> List.sort()

先将对象转为数组

然后第三行,调用Arrays.sort()

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

03 -> Arrays.sort(a, (Comparator) c)

针对泛型的排序方法有两个大分支,分别对应Collections.sort()的两个重载方法:

public static <T> void sort(T[] a, Comparator<? super T> c) {
    //调用默认Collections.sort(List<T> list)方法走if分支
    if (c == null) {
        sort(a);
    } 
    //调用带选择器Collections.sort(List<T> list,Comparator<? super T> c)方法走else分支
    else {
        //LegacyMergeSort.userRequested默认为false
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            //默认走这里
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}

通过我在注释里的说明,

我们同样可以暂时得出一个结论:

当我们调用带选择器的Collections.sort()方法,

可能会执行两种算法 归并排序、TimSort,

但由于
LegacyMergeSort.userRequested

默认为false,

所以最终会执行TimSort排序算法。

 

福利 " 上菜 "

不知不觉又到了经典"上菜"环节

兄弟们准备好了嘛!

今日菜系:

800道大厂面试题

1000道基础面试题

公众号"浩说编程"发送数字 “7”

找面试题再也不用东拼西凑爽到飞起!

 

关于这个TimSort,我简单看了一下源码,

知道兄弟们可能不想看大篇幅的代码,所以我这里只列出关键部分:

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,

                     T[] work, int workBase, int workLen) {

    .....

    // If array is small, do a "mini-TimSort" with no merges

    if (nRemaining < MIN_MERGE) {

        int initRunLen = countRunAndMakeAscending(a, lo, hi, c);

        binarySort(a, lo, hi, lo + initRunLen, c);

        return;

    }

    .....

    // Merge all remaining runs to complete sort

    assert lo == hi;

    ts.mergeForceCollapse();

    assert ts.stackSize == 1;

}

依然有两个分支,通过注释可以大体看出:
1、对于小量级数组,将执行叫做"mini-TimSort"的算法,我进入这个binarySort()方法发现其实是 插入排序。
2、否则就是 归并排序
所以兄弟们可以这样理解:

TimSort是基于归并排序 + 插入排序的优化算法!

以上是基于else分支得出的结论,接下来我们继续探讨if分支的逻辑:

c == null -> sort(a)

这里逻辑也很清晰,两种情况:
true: 归并排序
false(默认):ComparableTimSortpublic static void sort(Object[] a) {

    if (LegacyMergeSort.userRequested)

        legacyMergeSort(a);

    else

        //默认走这里

        ComparableTimSort.sort(a, 0, a.length, null, 0, 0);

}

这里的ComparableTimSort 和 Timsort

唯一的区别就是前者不需要自定义比较器。
 

泛型排序的分支比较多,我们再重新梳理一下逻辑:

01.Collections.sort(List list)

legacyMergeSort 归并排序

TimSort (默认)

02.带比较器Collections.sort(List list,Comparator<? super T> c)

legacyMergeSort 归并排序

ComparableTimSort(默认)

兄弟们发现没有

归并排序这个分支似乎没什么用

默认都是在使用TimSort。

而事实也确实如此,在legacyMergeSort方法里已经有注释,翻译一下大概就是"可能将在之后的版本移除",所以这个TimSort 应该是优于归并排序的更优解!

浅析:java的"排序"函数使用了哪些"算法"

关于Arrays.sort()应用的排序算法大家已经有了大致的了解

我再给兄弟们做一个整体总结,记得点赞分享

Arrays.sort()根据被排序数据的数据类型分为两种排序逻辑:

01 | 基本数据类型

具体排序算法取决于元素个数

< 47 插入排序

> 47 且·< 286 快排

> 286 归并排序

02 | 泛型

01.Collections.sort(List list)

legacyMergeSort 归并排序

TimSort (默认):归并 + 插入

02.带比较器 Collections.sort(List list,Comparator<? super T> c)

legacyMergeSort 归并排序

ComparableTimSort(默认) :归并 + 插入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值