大问题1 | Array最全源码分析(上)内部工具和数组排序

大问题1 | Array最全源码分析(一)内部工具和数组排序

本节的前置问题:小问题6 | Java中如何实现深拷贝?实现深拷贝的三种方法,因为其规模,不再视为是小问题。

版本:JDK8,首先声明一下,Arrays中方法为了支持基本参数类型以及引用参数类型,会有许多方法重载,大同小异,为省略篇幅,下面可能只写出其中一个。

内部工具
1. 默认比较器

NaturalOrder 类是一个静态内部类,实现了 Comparator 接口,并重写了 compare 方法来实现自然排序。 NaturalOrder 类还定义了一个静态的 INSTANCE 常量,可以在需要自然排序比较器的地方使用。

在 Arrays 类的实现中,如果没有提供比较器,就会使用 NaturalOrder.INSTANCE 进行排序。

static final class NaturalOrder implements Comparator<Object> {
    @SuppressWarnings("unchecked")
    public int compare(Object first, Object second) {
        return ((Comparable<Object>)first).compareTo(second);
    }
    static final NaturalOrder INSTANCE = new NaturalOrder();
}
2.传入参数范围检验方法

这段代码用于检查指定的索引范围是否在数组的有效范围内。如果不在范围内,就会抛出相应的异常。

rangeCheck 方法有三个参数:arrayLength 表示数组的长度,fromIndex 表示要访问的起始索引,toIndex 表示要访问的结束索引

(注: **结束索引不包含在内。 **如果要访问从 fromIndex 开始到 toIndex 结束的一段数组元素,只需要访问索引从 fromIndex 到 toIndex-1 的元素)

首先,该方法会检查 fromIndex 是否大于 toIndex,如果是,就会抛出 IllegalArgumentException 异常,表示起始索引不能大于结束索引。

然后,该方法会检查 fromIndex 是否小于 0,如果是,就会抛出 ArrayIndexOutOfBoundsException 异常,表示起始索引不能为负数。

最后,该方法会检查 toIndex 是否大于数组的长度,如果是,就会抛出 ArrayIndexOutOfBoundsException 异常,表示结束索引不能大于数组的长度。

    private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
        if (fromIndex > toIndex) {
            throw new IllegalArgumentException(
                    "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
        }
        if (fromIndex < 0) {
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        }
        if (toIndex > arrayLength) {
            throw new ArrayIndexOutOfBoundsException(toIndex);
        }
    }
提供给外部的工具
1. 基本类型数组排序(以int数组为例)

第一个 sort 方法用于对整个数组进行排序,它调用了 DualPivotQuicksort 类中的 sort 方法,并传入了整个数组的起始索引和结束索引。

第二个 sort 方法用于对指定范围内的数组元素进行排序,它调用了 DualPivotQuicksort 类中的 sort 方法,并传入了指定范围的起始索引和结束索引。和第一种方法的区别是需要对起始索引和结束索引进行检查,确保它们在数组的有效范围内。

注:这两个 sort 方法都使用了 Dual-Pivot Quicksort 算法,这是一种由 Vladimir Yaroslavskiy、Jon Bentley 和 Joshua Bloch 发明的快速排序算法。它的时间复杂度为 O(n log(n)),在许多数据集上表现优异,尤其是那些会导致其他快速排序算法性能下降的数据集。

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

public static void sort(int[] a, int fromIndex, int toIndex) {
    rangeCheck(a.length, fromIndex, toIndex);
    DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
2. 基本类型数组并行排序(以int数组为例)(Java8新增)

并行排序算法,基于Fork/Join 框架,将数组拆分,排序再组合。

第一个 parallelSort 方法用于对整个数组进行排序,它使用了 Fork/Join 框架,将数组分成若干个子数组,并在多个线程上同时对这些子数组进行排序,最后再将这些子数组归并到一起,形成一个有序数组。

如果数组长度小于并行排序的最小值(MIN_ARRAY_SORT_GRAN),则使用快速排序算法进行排序,不会进一步划分排序任务。

第二个 parallelSort 方法用于对指定范围内的数组元素进行排序,和第一种方法的区别是需要对起始索引和结束索引进行检查,确保它们在数组的有效范围内。

(注:

  1. 此处的并行排序的最小值(MIN_ARRAY_SORT_GRAN),在代码中被置为 1 << 13,其中 << 是位移运算符,表示将二进制数向左移动 13 位,相当于乘以 2^13,即 2 的 13 次方,结果为 8192。这个值通常是根据实际应用中的数据量和硬件环境来确定的,过小的值会导致内存争用,从而影响并行排序的效率和性能。

  2. ArraysParallelSortHelpers,包含了用于每种基本类型和对象类型的排序器和合并器实现。这些排序器和合并器的实现基本算法如下:

    1. 如果数组长度很小,直接使用顺序快速排序(通过 Arrays.sort 方法)。

    2. 否则,将数组分成两半,对每一半进行以下操作:

      a. 将这一半再分成两半(即四分之一)。

      b. 对这四个子数组进行排序。

      c. 将这四个子数组合并。

    3. 将这两半合并。)

	private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;

    public static void parallelSort(int[] a) {
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else
            new ArraysParallelSortHelpers.FJInt.Sorter
                (null, a, new int[n], 0, n, 0,
                 ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                 MIN_ARRAY_SORT_GRAN : g).invoke();
    }

        public static void parallelSort(int[] a, int fromIndex, int toIndex) {
            rangeCheck(a.length, fromIndex, toIndex);
            int n = toIndex - fromIndex, p, g;
            if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
                DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
            else
                new ArraysParallelSortHelpers.FJInt.Sorter
                    (null, a, new int[n], fromIndex, n, 0,
                     ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                     MIN_ARRAY_SORT_GRAN : g).invoke();
        }
3. 对象数组并行排序

将指定的对象数组按照升序排序,根据其元素Comparable的自然顺序。数组中的所有元素都必须实现Comparable接口。此外,数组中的所有元素必须是相互可比较的(即,对于数组中的任何元素e1和e2,e1.compareTo(e2)不应抛出ClassCastException)。

这种排序是 稳定的相等的元素在排序结果中的顺序不会改变

和上面的两种一样,第二个方法支持按范围排序,同时也需要调用rangeCheck(a.length, fromIndex, toIndex)来检查范围是否合法。

第三个方法支持指定比较器,如果没有提供比较器,那么将使用元素的自然顺序进行排序,第四个方法支持指定比较器的按范围排序。

@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> void parallelSort(T[] a) {
    int n = a.length, p, g;
    // 如果数组长度小于最小排序粒度或者公共池的并行级别为1,则使用TimSort进行排序
    if (n <= MIN_ARRAY_SORT_GRAN ||
        (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
        TimSort.sort(a, 0, n, NaturalOrder.INSTANCE, null, 0, 0);
    else
        // 否则,使用ForkJoin框架进行并行排序
        new ArraysParallelSortHelpers.FJObject.Sorter<T>
            (null, a,
             (T[])Array.newInstance(a.getClass().getComponentType(), n),
             0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
             MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke();
}

    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>>
    void parallelSort(T[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, fromIndex, toIndex, NaturalOrder.INSTANCE, null, 0, 0);
        else
            new ArraysParallelSortHelpers.FJObject.Sorter<T>
                (null, a,
                 (T[])Array.newInstance(a.getClass().getComponentType(), n),
                 fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                 MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke();
    }

    @SuppressWarnings("unchecked")
    public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,
                                        Comparator<? super T> cmp) {
        rangeCheck(a.length, fromIndex, toIndex);
        if (cmp == null)
            cmp = NaturalOrder.INSTANCE;
        int n = toIndex - fromIndex, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, fromIndex, toIndex, cmp, null, 0, 0);
        else
            new ArraysParallelSortHelpers.FJObject.Sorter<T>
                (null, a,
                 (T[])Array.newInstance(a.getClass().getComponentType(), n),
                 fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                 MIN_ARRAY_SORT_GRAN : g, cmp).invoke();
    }
4. 对象数组排序

对于非并行的对象数组排序,代码提供了两种排序实现:新的TimSort(ComparableTimSort.sort)和一个旧的归并排序(legacyMergeSort)。使用哪种实现由系统属性控制。排序是稳定的,相等的元素保持它们的相对顺序。

下面对代码进行解释:

  1. LegacyMergeSort 静态内部类:

    • 这个类包含一个静态布尔字段userRequested,用来决定是否应该使用旧的归并排序实现。
    • userRequested的值是通过读取一个名为"java.util.Arrays.useLegacyMergeSort"的系统属性来设置的,使用AccessController.doPrivileged方法可以在必要的安全权限下读取该属性。
    • 注释指出,这个旧的归并排序是为了兼容性而保留的,应该在将来的版本中移除。
  2. sort(Object[] a) 方法:

    • 这是主要的排序方法,它接受一个对象数组a作为参数。
    • 方法首先检查userRequested标志是否为真,这将表明应该使用旧的归并排序实现。如果是这样,它会调用legacyMergeSort方法。
    • 如果userRequested为假,它使用ComparableTimSort.sort方法对数组进行排序。
  3. legacyMergeSort方法:

    • 这是一个私有方法,实现了旧的归并排序算法。
    • 它将原始数组a克隆到一个新的数组aux中,然后调用mergeSort方法,传递辅助数组、原始数组和要排序的索引范围。
    • 注释表明这个方法已经被弃用,并将在未来的版本中移除。
  4. mergeSort方法

    1. mergeSort方法的参数:

      Object[] src: 源数组,即要排序的数组。

      Object[] dest: 目标数组,用于存放排序的结果,可能比源数组大,并且可能有偏移量。

      int low: 目标数组中开始排序的索引。

      int high: 目标数组中结束排序的索引。

      int off: 偏移量,用于在源数组中生成对应的lowhigh索引。

      length变量计算需要排序的元素数量。

    2. 如果数组的长度小于INSERTIONSORT_THRESHOLD(阈值,定义为7),则使用插入排序而不是归并排序。插入排序在小数组上效率较高。

    3. 如果数组长度大于等于阈值,则递归地对数组的两半进行排序:

    4. 计算中点mid

    5. dest数组的前半部分进行排序,结果存放在src数组中。

    6. dest数组的后半部分进行排序,结果也存放在src数组中。

    7. 检查是否需要合并:

      如果src数组中间的两个元素已经有序(src[mid-1] <= src[mid]),则不需要合并,直接将src的这部分复制到dest数组中。

      如果不是有序的,则需要将src数组中已排序的两半合并到dest数组中。

    8. 合并过程:

      使用两个指针pq分别指向src数组中两个有序部分的开始。

      遍历dest数组,比较src中的元素,并将较小的元素复制到dest数组中,直到所有元素都被合并。

      注:swap方法用于交换数组中两个元素的位置。

同时,和之前的几种排序一样,对对象的sort方法也支持对指定范围和指定比较器的排序

下附源码:

    static final class LegacyMergeSort {
        private static final boolean userRequested =
            java.security.AccessController.doPrivileged(
                new sun.security.action.GetBooleanAction(
                    "java.util.Arrays.useLegacyMergeSort")).booleanValue();
    }

    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

    /** To be removed in a future release. */
    private static void legacyMergeSort(Object[] a) {
        Object[] aux = a.clone();
        mergeSort(aux, a, 0, a.length, 0);
    }

public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex);
        else
            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
    }

    /** To be removed in a future release. */
    private static void legacyMergeSort(Object[] a,
                                        int fromIndex, int toIndex) {
        Object[] aux = copyOfRange(a, fromIndex, toIndex);
        mergeSort(aux, a, fromIndex, toIndex, -fromIndex);
    }

    /**
     * Tuning parameter: list size at or below which insertion sort will be
     * used in preference to mergesort.
     * To be removed in a future release.
     */
    private static final int INSERTIONSORT_THRESHOLD = 7;

 @SuppressWarnings({"unchecked", "rawtypes"})
    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

    /**
     * Swaps x[a] with x[b].
     */
    private static void swap(Object[] x, int a, int b) {
        Object t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

 public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
    /** To be removed in a future release. */
    private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
        T[] aux = a.clone();
        if (c==null)
            mergeSort(aux, a, 0, a.length, 0);
        else
            mergeSort(aux, a, 0, a.length, 0, c);
    }

@SuppressWarnings({"rawtypes", "unchecked"})
    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);

        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

读完本文的朋友可能留下很多问题:

TimSort和Dual-Pivot Quicksort 算法是什么?实现原理和区别是什么?

Fork/Join 框架是什么?

Java 中的移位运算符有什么用?

AccessController和背后的Java安全框架是什么?

关于Array其他源码的介绍和上述的哪些问题,我们继续看。

————————————————————

本专栏是【小问题】系列,旨在每篇解决一个小问题,并秉持着刨根问底的态度解决这个问题可能带出的一系列问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值