欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Arrays源码解析。

程序员文章站 2024-03-06 21:21:08
...
/**
 * 此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。
 *
 * 除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 NullPointerException。
 *
 * 此类中所含方法的文档都包括对实现 的简短描述。应该将这些描述视为实现注意事项,而不应将它们视为规范 的一部分。
 * 实现者应该可以随意替代其他算法,只要遵循规范本身即可。
 * (例如,sort(Object[]) 使用的算法不必是一个合并排序算法,但它必须是稳定的。)
 */
public class Arrays {
    /**
     * 并行排序算法不能进一步划分排序任务的最小数组长度。
     * 使用较小的大小通常会导致任务之间的内存争用,从而使并行加速变得不太可能。
     */
    private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;

    /**
     *  取消默认构造函数,确保不可实例化。
     */
    private Arrays() {}

    /**
     * 实现一组相互比较元素的自然顺序的比较器。
     * 当提供的比较器为空时可以使用。为了简化底层实现中的代码共享,compare方法仅为其第二个参数声明类型对象。
     *
     * 数组类实现者的注意:
     * 相对于这个比较器使用的TimSort, ComparableTimSort是否具有任何性能优势,这是一个经验问题。
     * 如果不是,最好删除或绕过ComparableTimSort。
     * 目前还没有分离它们进行并行排序的经验案例,因此所有公共对象并行排序方法都使用相同的基于实现。
     *
     */
    static final class NaturalOrder implements Comparator<Object> {
        @SuppressWarnings("unchecked")
        public int compare(Object first, Object second) {
            return ((Comparable<Object>)first).compareTo(second);
        }
        static final java.util.Arrays.NaturalOrder INSTANCE = new java.util.Arrays.NaturalOrder();
    }

    /**
     * 检查fromIndex和toIndex是否在范围内,如果不在则抛出异常。
     * @param arrayLength   要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
        if (fromIndex > toIndex) { // 如果 fromIndex > toIndex
            throw new IllegalArgumentException(
                    "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
        }
        if (fromIndex < 0) { // 如果 fromIndex < 0
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        }
        if (toIndex > arrayLength) { // 如果toIndex > a.length
            throw new ArrayIndexOutOfBoundsException(toIndex);
        }
    }

    /**
     *  对指定的 int 型数组按数字升序进行排序。
     *  该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     * 对指定 int 型数组的指定范围按数字升序进行排序。排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。
     * (如果 fromIndex==toIndex,则排序范围为空。)
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(int[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     * 对指定的 long 型数组按数字升序进行排序。
     * 该排序算法是一个经过调优的快速排序法, 此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(long[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     * 对指定 long 型数组的指定范围按数字升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(long[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     * 对指定的 short 型数组按数字升序进行排序。
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(short[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     * 对指定 short 型数组的指定范围按数字升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(short[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     * 对指定的 char 型数组按数字升序进行排序。
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(char[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     * 对指定 char 型数组的指定范围按数字升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(char[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     * 对指定的 byte 型数组按数字升序进行排序。
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(byte[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1);
    }

    /**
     * 对指定 byte 型数组的指定范围按数字升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(byte[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
    }

    /**
     * 对指定的 float 型数组按数字升序进行排序。
     * 虽然 < 关系式对不同数字 -0.0f == 0.0f 返回的结果为 true,并且认为 NaN 值既不小于或大于任何浮点值,
     * 也不等于任何浮点值,甚至不等于它自身。但 < 关系式不能提供所有浮点值的整体排序。为了允许进行排序,
     * 此方法不使用 < 关系式来确定数字升序排序,而是利用 Float.compareTo(java.lang.Float) 来完成整体排序。
     * 此排序法不同于 < 关系式,其中 -0.0f 被认为是小于 0.0f 的值,并且 NaN 被认为大于其他任何浮点值。
     * 为了进行排序,所有 NaN 值都被认为是等效且相等的。
     *
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(float[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     *  对指定 float 型数组的指定范围按数字升序进行排序。
     *  排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。
     *  (如果 fromIndex==toIndex,则排序范围为空。)
     * 虽然 < 关系式对不同数字 -0.0f == 0.0f 返回的结果为 true,并且认为 NaN 值既不小于或大于任何浮点值,
     * 也不等于任何浮点值,甚至不等于它自身。但 < 关系式不能提供所有浮点值的整体排序。为了允许进行排序,
     * 此方法不使用 < 关系式来确定数字升序排序,而是利用 Float.compareTo(java.lang.Float) 来完成整体排序。
     * 此排序法不同于 < 关系式,其中 -0.0f 被认为是小于 0.0f 的值,并且 NaN 被认为大于其他任何浮点值。
     * 为了进行排序,所有 NaN 值都被认为是等效且相等的。
     *
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(float[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     *  对指定的 double 型数组按数字升序进行排序。
     * 虽然 < 关系式对不同数字 -0.0 == 0.0 返回的结果为 true,并且认为 NaN 值既不小于或大于任何浮点值,
     * 也不等于任何浮点值,甚至不等于它自身。但 < 关系式不能提供所有浮点值的整体排序。为了允许进行排序,
     * 此方法不使用 < 关系式来确定数字升序排序,而是利用 Double.compareTo(java.lang.Double) 来完成整体排序。
     * 此排序法不同于 < 关系式,其中 -0.0 被认为是小于 0.0 的值,并且 NaN 被认为大于其他任何浮点值。
     * 为了进行排序,所有 NaN 值都被认为是等效且相等的。
     *
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     */
    public static void sort(double[] a) {
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }

    /**
     * 对指定 double 型数组的指定范围按数字升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 虽然 < 关系式对不同数字 -0.0 == 0.0 返回的结果为 true,并且认为 NaN 值既不小于或大于任何浮点值,
     * 也不等于任何浮点值,甚至不等于它自身。但 < 关系式不能提供所有浮点值的整体排序。为了允许进行排序,
     * 此方法不使用 < 关系式来确定数字升序排序,而是利用 Double.compareTo(java.lang.Double) 来完成整体排序。
     * 此排序法不同于 < 关系式,其中 -0.0 被认为是小于 0.0 的值,并且 NaN 被认为大于其他任何浮点值。
     * 为了进行排序,所有 NaN 值都被认为是等效且相等的。
     *
     * 该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(double[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
    }

    /**
     * 将指定的数组按升序排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(byte[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * ForkJoinPool.commonPool()用于执行任何并行任务。
     * @param a 要排序的数组
     */
    public static void parallelSort(byte[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1);
        else // 并行排序
            new ArraysParallelSortHelpers.FJByte.Sorter
                    (null, a, new byte[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(byte[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * ForkJoinPool.commonPool()用于执行任何并行任务。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(byte[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJByte.Sorter
                    (null, a, new byte[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将指定的数组按升序排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(char[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(char[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(char[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJChar.Sorter
                    (null, a, new char[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(char[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(char[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(char[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
        else     // 并行排序
            new ArraysParallelSortHelpers.FJChar.Sorter
                    (null, a, new char[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将指定的数组按升序排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(short[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(short[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(short[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else     // 并行排序
            new ArraysParallelSortHelpers.FJShort.Sorter
                    (null, a, new short[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(short[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(short[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(short[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJShort.Sorter
                    (null, a, new short[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将指定的数组按升序排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(int[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(int[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(int[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        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();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(int[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(int[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(int[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        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();
    }

    /**
     * 将指定的数组按升序排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(long[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(long[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(long[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJLong.Sorter
                    (null, a, new long[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(long[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(long[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(long[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJLong.Sorter
                    (null, a, new long[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将指定的数组按升序排序。
     * <关系不提供所有浮点值的总顺序:-0.0f == 0.0f是true,而float.NaN的值既不小于、也不大于、也不等于任何值,甚至本身。
     * 该方法使用方法Float.compareTo施加的总顺序:-0.0f被视为小于0.0f值,Float.NaN被视为大于任何其他值,
     * 所有Float.NaN值被视为相等。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(float[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(float[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(float[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else    // 并行排序
            new ArraysParallelSortHelpers.FJFloat.Sorter
                    (null, a, new float[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * 将指定的数组按升序排序。
     * <关系不提供所有浮点值的总顺序:-0.0f == 0.0f是true,而float.NaN的值既不小于、也不大于、也不等于任何值,甚至本身。
     * 该方法使用方法Float.compareTo施加的总顺序:-0.0f被视为小于0.0f值,Float.NaN被视为大于任何其他值,
     * 所有Float.NaN值被视为相等。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(float[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(float[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(float[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
        else     // 并行排序
            new ArraysParallelSortHelpers.FJFloat.Sorter
                    (null, a, new float[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将指定的数组按升序排序。
     *
     * <关系不提供所有双精度值的总顺序:-0.0d == 0.0d是true,
     * 而Double.NaN的值既不小于、也不大于、也不等于任何值,甚至自身。
     * 该方法使用由方法Double.compareTo施加的总顺序:-0.0d被视为小于0.0d值,
     * Double.NaN被视为大于任何其他值,并且所有Double.NaN值都被视为相等。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(double[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(double[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     */
    public static void parallelSort(double[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
        else     // 并行排序
            new ArraysParallelSortHelpers.FJDouble.Sorter
                    (null, a, new double[n], 0, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 将数组的指定范围按升序排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排除)。
     * 如果fromIndex == toIndex,则要排序的范围为空。
     *
     * <关系不提供所有双精度值的总顺序:-0.0d == 0.0d是true,
     * 而Double.NaN的值既不小于、也不大于、也不等于任何值,甚至自身。
     * 该方法使用由方法Double.compareTo施加的总顺序:-0.0d被视为小于0.0d值,
     * Double.NaN被视为大于任何其他值,并且所有Double.NaN值都被视为相等。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(double[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(double[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void parallelSort(double[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
        else // 并行排序
            new ArraysParallelSortHelpers.FJDouble.Sorter
                    (null, a, new double[n], fromIndex, n, 0,
                            ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
                                    MIN_ARRAY_SORT_GRAN : g).invoke();
    }

    /**
     * 根据元素的自然顺序,将指定的对象数组按升序排序。
     * 数组中的所有元素都必须实现Comparable接口。
     * 此外,数组中的所有元素必须相互可比(即,e1. compareto (e2)不能为数组中的任何元素e1和e2抛出ClassCastException)。
     *
     * 这种排序保证是稳定的:相等的元素不会因为排序而重新排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(Object[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(Object[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param <T>   要排序的对象的类
     */
    public static <T extends Comparable<? super T>> void parallelSort(T[] a) {
        int n = a.length, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, 0, n, java.util.Arrays.NaturalOrder.INSTANCE, null, 0, 0);
        else // 并行排序
            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, java.util.Arrays.NaturalOrder.INSTANCE).invoke();
    }

    /**
     * 将指定对象数组的指定范围按照其可比较的自然顺序升序排列元素。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排他)。
     * (如果fromIndex==toIndex,则要排序的范围为空。)此范围内的所有元素都必须实现可比接口。
     * 此外,此范围内的所有元素必须相互可比
     * (也就是说,e1.compareTo(e2)不能对数组中的任何e1和e2元素抛出ClassCastException)。
     *
     * 这种排序保证是稳定的:相等的元素不会因为排序而重新排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(Object[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(Object[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param <T>   要排序的对象的类
     */
    @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;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, fromIndex, toIndex, java.util.Arrays.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, java.util.Arrays.NaturalOrder.INSTANCE).invoke();
    }

    /**
     * 根据指定比较器产生的顺序对指定的对象数组进行排序。
     * 通过指定的比较器,数组中的所有元素必须相互可比
     * (即,c.compare(e1, e2)不能为数组中的任何元素e1和e2抛出ClassCastException)。
     *
     * 这种排序保证是稳定的:相等的元素不会因为排序而重新排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(Object[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(Object[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param cmp   比较器来确定数组的顺序。空值表示应该使用元素的可比较自然顺序。
     * @param <T>   要排序的对象的类
     */
    @SuppressWarnings("unchecked")
    public static <T> void parallelSort(T[] a, Comparator<? super T> cmp) {
        if (cmp == null) // 比较器为空,默认使用元素的可比较自然顺序。
            cmp = java.util.Arrays.NaturalOrder.INSTANCE;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
                (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            TimSort.sort(a, 0, n, cmp, null, 0, 0);
        else// 并行排序
            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, cmp).invoke();
    }

    /**
     * 根据指定比较器所诱导的顺序,对指定对象数组的指定范围进行排序。
     * 要排序的范围从索引fromIndex(包括)扩展到索引toIndex(排他)。
     * (如果fromIndex==toIndex,则要排序的范围为空。)
     * 通过指定的比较器,范围内的所有元素必须相互可比(即,c.compare(e1, e2)不能对范围内的任何元素e1和e2抛出ClassCastException)。
     *
     * 这种排序保证是稳定的:相等的元素不会因为排序而重新排序。
     *
     * @implNote 排序算法是一种并行的排序-归并算法,它将数组分解成子数组,子数组本身先进行排序,然后再合并。
     * 当子数组长度达到最小粒度时,将使用适当的Arrays.sort(Object[])方法对子数组进行排序。
     * 如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort(Object[])方法对其进行排序。
     * 该算法需要的工作空间不大于原始数组的大小。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param cmp   比较器来确定数组的顺序。空值表示应该使用元素的可比较自然顺序。
     * @param <T>   要排序的对象的类
     */
    @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 = java.util.Arrays.NaturalOrder.INSTANCE;
        int n = toIndex - fromIndex, p, g;
        // 如果数组的长度小于最小粒度,则使用适当的Arrays.sort(byte[])方法对其进行排序。
        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();
    }

    /*
     * 复杂类型数组的排序。
     */

    /**
     * 可以使用系统属性选择旧的归并排序实现(为了与损坏的比较器兼容)。
     * 由于循环依赖关系,在封闭类中不能是静态布尔值。将在未来的版本中删除。
     */
    static final class LegacyMergeSort {
        private static final boolean userRequested =
                java.security.AccessController.doPrivileged(
                        new sun.security.action.GetBooleanAction(
                                "java.util.Arrays.useLegacyMergeSort")).booleanValue();
    }

    /**
     *  根据元素的自然顺序对指定对象数组按升序进行排序。数组中的所有元素都必须实现 Comparable 接口。
     *  此外,数组中的所有元素都必须是可相互比较的(也就是说,对于数组中的任何 e1 和 e2 元素而言,
     *  e1.compareTo(e2) 不得抛出 ClassCastException)。
     * 保证此排序是稳定的:不会因调用 sort 方法而对相等的元素进行重新排序。
     *
     * 该排序算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并)。
     * 此算法提供可保证的 n*log(n) 性能。
     * @param a 要排序的数组
     */
    public static void sort(Object[] a) {
        if (java.util.Arrays.LegacyMergeSort.userRequested)
            legacyMergeSort(a); // 遗留归并排序
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

    /**
     * 根据元素的自然顺序对指定对象数组按升序进行排序遗留归并排序
     *
     * 将在未来的版本中删除。
     * @param a 要排序的数组
     */
    private static void legacyMergeSort(Object[] a) {
        Object[] aux = a.clone();
        mergeSort(aux, a, 0, a.length, 0); // 归并排序
    }

    /**
     * 根据元素的自然顺序对指定对象数组的指定范围按升序进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 此范围中的所有元素都必须实现 Comparable 接口。此外,此范围中的所有元素都必须是可相互比较的
     * (也就是说,对于数组中的任何 e1 和 e2 元素而言,e1.compareTo(e2) 不得抛出 ClassCastException)。
     * 保证此排序是稳定的:不会因调用 sort 方法而对相等的元素进行重新排序。
     *
     * 该排序算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并)。
     * 此算法提供可保证的 n*log(n) 性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        if (java.util.Arrays.LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex); // 遗留归并排序
        else
            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
    }

    /**
     *  根据元素的自然顺序对指定对象数组的指定范围按升序进行遗留归并排序
     *
     *  将在未来的版本中删除。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     */
    private static void legacyMergeSort(Object[] a,
                                        int fromIndex, int toIndex) {
        Object[] aux = copyOfRange(a, fromIndex, toIndex); // 将指定数组的指定范围复制到一个新数组。
        mergeSort(aux, a, fromIndex, toIndex, -fromIndex); // 归并排序
    }

    /**
     * 调优参数:列表大小达到或者低于使用哪个插入排序,归并排序。
     * 将在未来的版本中删除。
     */
    private static final int INSERTIONSORT_THRESHOLD = 7;

    /**
     * 归并排序
     * 将在未来的版本中删除。
     * @param src   源数组。
     * @param dest  目标数组。
     * @param low   在dest中开始排序的索引
     * @param high  dest结束排序的结束索引
     * @param off   在src中生成相应的低、高的偏移量
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low; // 需要排序数组长度

        // 最小数组上的插入排序
        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); // 用x[a]交换x[b]。
            return;
        }

        // 递归地将dest的一半排序为src
        int destLow  = low; // 在dest中开始排序的索引
        int destHigh = high; // dest结束排序的结束索引
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off); // 归并排序
        mergeSort(dest, src, mid, high, -off); // 归并排序

        // 如果列表已经排序,只需从src复制到dest。
        // 这是一种优化,可以为几乎有序的列表带来更快的排序。
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // 合并排序的一半(现在在src)到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++];
        }
    }

    /**
     * 用x[a]交换x[b]。
     * @param x 可能具有偏移量的(可能更大的)阵列目标
     * @param a 数组索引
     * @param b 数组索引
     */
    private static void swap(Object[] x, int a, int b) {
        Object t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

    /**
     * 根据指定比较器产生的顺序对指定对象数组进行排序。数组中的所有元素都必须是通过指定比较器可相互比较的
     * (也就是说,对于数组中的任何 e1 和 e2 元素而言,c.compare(e1, e2) 不得抛出 ClassCastException)。
     *
     * 保证此排序是稳定的:不会因调用 sort 方法而对相等的元素进行重新排序。
     *
     * 该排序算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并)。
     * 此算法提供可保证的 n*log(n) 性能。
     * @param a 要排序的数组
     * @param c 确定数组顺序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   要排序的对象的类
     */
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) { // 根据元素的自然顺序对指定对象数组按升序进行排序。
            sort(a);
        } else {
            if (java.util.Arrays.LegacyMergeSort.userRequested)
                legacyMergeSort(a, c); // 遗留归并排序
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

    /**
     * 根据指定比较器产生的顺序对指定对象数组进行遗留归并排序
     *
     * 将在未来的版本中删除。
     * @param a     要排序的数组
     * @param c     确定数组顺序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   要排序的对象的类
     */
    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);
    }

    /**
     * 根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
     * 排序的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则排序范围为空。)
     * 此范围内的所有元素都必须是通过指定比较器可相互比较的(也就是说,对于该范围中的任何 e1 和 e2 元素而言,
     * c.compare(e1, e2) 不得抛出 ClassCastException)。
     * 保证此排序是稳定的:不会因调用 sort 方法而对相等的元素进行重新排序。
     *
     * 该排序算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并)。
     * 此算法提供可保证的 n*log(n) 性能。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param c 确定数组顺序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   要排序的对象的类
     */
    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) { // 根据元素的自然顺序对指定对象数组的指定范围按升序进行排序。
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            // 根据指定比较器产生的顺序对指定对象数组的指定范围进行归并排序
            if (java.util.Arrays.LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

    /**
     * 根据指定比较器产生的顺序对指定对象数组的指定范围进行归并排序
     *
     * 将在未来的版本中删除。
     * @param a 要排序的数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param c 确定数组顺序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   要排序的对象的类
     */
    private static <T> void legacyMergeSort(T[] a, int fromIndex, int toIndex,
                                            Comparator<? super T> c) {
        T[] aux = copyOfRange(a, fromIndex, toIndex); // 将指定数组的指定范围复制到一个新数组。
        if (c==null) // 自然顺序进行归并排序
            mergeSort(aux, a, fromIndex, toIndex, -fromIndex);
        else // 根据指定比较器产生的顺序进行归并排序
            mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c);
    }

    /**
     * 根据指定比较器产生的顺序进行归并排序
     *
     * 将在未来的版本中删除。
     *
     * @param src   源数组。
     * @param dest  目标数组
     * @param low    在dest中开始排序的索引
     * @param high  dest结束排序的结束索引
     * @param off   src中与dest低值对应的偏移量
     * @param c 确定数组顺序的比较器。null 值指示应该使用元素的自然顺序。
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low; // 数组长度

        // 最小数组上的插入排序
        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); // 用x[a]交换x[b]。
            return;
        }

        // 递归地将dest的一半排序为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);// 根据指定比较器产生的顺序进行归并排序

        // 如果列表已经排序,只需从src复制到dest。
        // 这是一种优化,可以为几乎有序的列表带来更快的排序。
        if (c.compare(src[mid-1], src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // 合并排序的一半(现在在src)到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++];
        }
    }

    // 并行前缀

    /**
     * 使用提供的函数并行地累积给定数组中的每个元素。
     * 例如,如果数组最初持有[2,1,0,3],并且操作执行加法,那么返回时数组持有[2,3,3,6]。
     * 对于大型数组,并行前缀计算通常比顺序循环更有效。
     *
     * @param array 数组,该数组将被此方法就地修改
     * @param op    一种无副作用的,用于执行累积的联想功能
     * @param <T>   数组中对象的类
     */
    public static <T> void parallelPrefix(T[] array, BinaryOperator<T> op) {
        Objects.requireNonNull(op);
        if (array.length > 0)
            new ArrayPrefixHelpers.CumulateTask<>
                    (null, op, array, 0, array.length).invoke();
    }

    /**
     * 对数组的给定子范围执行parallelPrefix(Object[], BinaryOperator)。
     *
     * @param array 数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param op    一种无副作用的,用于执行累积的联想功能
     * @param <T>   数组中对象的类
     */
    public static <T> void parallelPrefix(T[] array, int fromIndex,
                                          int toIndex, BinaryOperator<T> op) {
        Objects.requireNonNull(op);
        rangeCheck(array.length, fromIndex, toIndex);
        if (fromIndex < toIndex)
            new ArrayPrefixHelpers.CumulateTask<>
                    (null, op, array, fromIndex, toIndex).invoke();
    }

    /**
     * 使用提供的函数并行地累积给定数组中的每个元素。
     * 例如,如果数组最初持有[2,1,0,3],并且操作执行加法,那么返回时数组持有[2,3,3,6]。
     * 对于大型数组,并行前缀计算通常比顺序循环更有效。
     *
     * @param array 数组,该数组将被此方法就地修改
     * @param op    一种无副作用的,用于执行累积的联想功能
     */
    public static void parallelPrefix(long[] array, LongBinaryOperator op) {
        Objects.requireNonNull(op);
        if (array.length > 0)
            new ArrayPrefixHelpers.LongCumulateTask
                    (null, op, array, 0, array.length).invoke();
    }

    /**
     * 对给定的数组子范围执行parallelPrefix(long[], LongBinaryOperator)。
     * @param array 数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param op    一种无副作用的,用于执行累积的联想功能
     */
    public static void parallelPrefix(long[] array, int fromIndex,
                                      int toIndex, LongBinaryOperator op) {
        Objects.requireNonNull(op);
        rangeCheck(array.length, fromIndex, toIndex);
        if (fromIndex < toIndex)
            new ArrayPrefixHelpers.LongCumulateTask
                    (null, op, array, fromIndex, toIndex).invoke();
    }

    /**
     * 使用提供的函数并行地累积给定数组中的每个元素。
     * 例如,如果数组最初持有[2.0,1.0,0.0,3.0],并且操作执行加法,那么返回时数组持有[2.0,3.0,3.0,6.0]。
     * 对于大型数组,并行前缀计算通常比顺序循环更有效。
     *
     * 因为浮点操作可能不是严格关联的,所以返回的结果可能与按顺序执行操作所得到的值不相同。
     * @param array 数组,该数组将被此方法就地修改
     * @param op    执行累积的无副作用的函数
     */
    public static void parallelPrefix(double[] array, DoubleBinaryOperator op) {
        Objects.requireNonNull(op);
        if (array.length > 0)
            new ArrayPrefixHelpers.DoubleCumulateTask
                    (null, op, array, 0, array.length).invoke();
    }

    /**
     * 对给定的数组子范围执行parallelPrefix(double[], DoubleBinaryOperator)。
     * @param array 数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param op    一种无副作用的,用于执行累积的联想功能
     */
    public static void parallelPrefix(double[] array, int fromIndex,
                                      int toIndex, DoubleBinaryOperator op) {
        Objects.requireNonNull(op);
        rangeCheck(array.length, fromIndex, toIndex);
        if (fromIndex < toIndex)
            new ArrayPrefixHelpers.DoubleCumulateTask
                    (null, op, array, fromIndex, toIndex).invoke();
    }

    /**
     * 使用提供的函数并行地累积给定数组中的每个元素。
     * 例如,如果数组最初持有[2,1,0,3],并且操作执行加法,那么返回时数组持有[2,3,3,6]。
     * 对于大型数组,并行前缀计算通常比顺序循环更有效。
     * @param array 数组,该数组将被此方法就地修改
     * @param op    一种无副作用的,用于执行累积的联想功能
     */
    public static void parallelPrefix(int[] array, IntBinaryOperator op) {
        Objects.requireNonNull(op);
        if (array.length > 0)
            new ArrayPrefixHelpers.IntCumulateTask
                    (null, op, array, 0, array.length).invoke();
    }

    /**
     * 对给定的数组子范围执行parallelPrefix(int[], IntBinaryOperator)。
     * @param array 数组
     * @param fromIndex 要排序的第一个元素的索引(包括)
     * @param toIndex   要排序的最后一个元素的索引(不包括)
     * @param op    一种无副作用的,用于执行累积的联想功能
     */
    public static void parallelPrefix(int[] array, int fromIndex,
                                      int toIndex, IntBinaryOperator op) {
        Objects.requireNonNull(op);
        rangeCheck(array.length, fromIndex, toIndex);
        if (fromIndex < toIndex)
            new ArrayPrefixHelpers.IntCumulateTask
                    (null, op, array, fromIndex, toIndex).invoke();
    }

    /**
     * 使用二分搜索法来搜索指定的 long 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(long[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return 如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,
     *      则为 a.length。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(long[] a, long key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 long 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(long[], int, int) 方法)。如果没有对范围进行排序,
     * 则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(long[] a, int fromIndex, int toIndex,
                                   long key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key); // 使用二分搜索法来搜索指定的 long 型数组,以获得指定的值。
    }

    /**
     * 使用二分搜索法来搜索指定的 long 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(long[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return 如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(long[] a, int fromIndex, int toIndex, long key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            long midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     *  使用二分搜索法来搜索指定的 int 型数组,以获得指定的值。
     *  必须在进行此调用之前对数组进行排序(通过 sort(int[]) 方法)。
     *  如果没有对数组进行排序,则结果是不确定的。如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return  如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,
     *      则为 a.length。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     *  使用二分搜索法来搜索指定的 int 型数组的范围,以获得指定的值。
     *  必须在进行此调用之前对范围进行排序(通过 sort(int[], int, int) 方法)。如果没有对范围进行排序,
     *  则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(int[] a, int fromIndex, int toIndex, int key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     *  使用二分搜索法来搜索指定的 int 型数组的范围,以获得指定的值。
     *  必须在进行此调用之前对范围进行排序(通过 sort(int[], int, int) 方法)。如果没有对范围进行排序,
     *  则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     *  像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定的 short 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(short[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return  如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,
     *      则为 a.length。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(short[] a, short key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 short 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(short[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(short[] a, int fromIndex, int toIndex,
                                   short key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 short 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(short[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(short[] a, int fromIndex, int toIndex,
                                     short key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            short midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定的 char 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(char[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return  如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(char[] a, char key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 char 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(char[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(char[] a, int fromIndex, int toIndex,
                                   char key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 char 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(char[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(char[] a, int fromIndex, int toIndex,
                                     char key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            char midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(byte[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return 如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(byte[] a, byte key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 byte 型数组的范围,
     * 以获得指定的值。必须在进行此调用之前对范围进行排序(通过 sort(byte[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(byte[] a, int fromIndex, int toIndex, byte key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 byte 型数组的范围,
     * 以获得指定的值。必须在进行此调用之前对范围进行排序(通过 sort(byte[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(byte[] a, int fromIndex, int toIndex,
                                     byte key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            byte midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1); // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定的 double 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(double[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。此方法认为所有 NaN 值都是等效且相等的。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return 如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(double[] a, double key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 double 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(double[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * 此方法认为所有 NaN 值都是等效且相等的。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(double[] a, int fromIndex, int toIndex,
                                   double key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 double 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(double[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。
     * 此方法认为所有 NaN 值都是等效且相等的。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(double[] a, int fromIndex, int toIndex,
                                     double key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            double midVal = a[mid];

            if (midVal < key)
                low = mid + 1;  // 两个val都不是NaN,这个val更小
            else if (midVal > key)
                high = mid - 1; // 两个val都不是NaN,这个val更大
            else {
                long midBits = Double.doubleToLongBits(midVal); // 获取中间值的位
                long keyBits = Double.doubleToLongBits(key);    // 获取要搜索的值的位
                if (midBits == keyBits)     // 值相等
                    return mid;             // 找到关键字
                else if (midBits < keyBits) // (-0.0, 0.0) or (!NaN, NaN)
                    low = mid + 1;
                else                        // (0.0, -0.0) or (NaN, !NaN)
                    high = mid - 1;
            }
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定的 float 型数组,以获得指定的值。
     * 必须在进行此调用之前对数组进行排序(通过 sort(float[]) 方法)。如果没有对数组进行排序,则结果是不确定的。
     * 如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。此方法认为所有 NaN 值都是等效且相等的。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return  如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(float[] a, float key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 float 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(float[], int, int) 方法)。如果没有对范围进行排序,则结果是不确定的。
     * 如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。此方法认为所有 NaN 值都是等效且相等的。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(float[] a, int fromIndex, int toIndex,
                                   float key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定的 float 型数组的范围,以获得指定的值。
     * 必须在进行此调用之前对范围进行排序(通过 sort(float[], int, int) 方法)。如果没有对范围进行排序,则结果是不确定的。
     * 如果范围包含多个带有指定值的元素,则无法保证找到的是哪一个。此方法认为所有 NaN 值都是等效且相等的。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(float[] a, int fromIndex, int toIndex,
                                     float key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            float midVal = a[mid];

            if (midVal < key)
                low = mid + 1;  // 两个val都不是NaN,这个val更小
            else if (midVal > key)
                high = mid - 1; // N两个val都不是NaN,这个val更大
            else {
                int midBits = Float.floatToIntBits(midVal); // 获取中间的值的位
                int keyBits = Float.floatToIntBits(key); // 获取要搜索的值的位
                if (midBits == keyBits)     // 值相等
                    return mid;             // 找到关键字
                else if (midBits < keyBits) // (-0.0, 0.0) or (!NaN, NaN)
                    low = mid + 1;
                else                        // (0.0, -0.0) or (NaN, !NaN)
                    high = mid - 1;
            }
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定数组,以获得指定对象。
     * 在进行此调用之前,必须根据元素的自然顺序对数组进行升序排序(通过 sort(Object[]) 方法)。如果没有对数组进行排序,
     * 则结果是不确定的。(如果数组包含不可相互比较的元素(例如,字符串和整数),
     * 则无法根据其元素的自然顺序对数组进行排序,因此结果是不确定的。)
     * 如果数组包含多个等于指定对象的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @return 如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(Object[] a, Object key) {
        return binarySearch0(a, 0, a.length, key);
    }

    /**
     * 使用二分搜索法来搜索指定数组的范围,以获得指定对象。
     * 在进行此调用之前,必须根据元素的自然顺序对范围进行升序排序(通过 sort(Object[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。(如果范围包含不可相互比较的元素,例如,字符串和整数,
     * 则无法 根据其元素的自然顺序对范围进行排序,因此结果是不确定的。)如果范围包含多个等于指定对象的元素,
     * 则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static int binarySearch(Object[] a, int fromIndex, int toIndex,
                                   Object key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    /**
     * 使用二分搜索法来搜索指定数组的范围,以获得指定对象。
     * 在进行此调用之前,必须根据元素的自然顺序对范围进行升序排序(通过 sort(Object[], int, int) 方法)。
     * 如果没有对范围进行排序,则结果是不确定的。(如果范围包含不可相互比较的元素,例如,字符串和整数,
     * 则无法 根据其元素的自然顺序对范围进行排序,因此结果是不确定的。)如果范围包含多个等于指定对象的元素,
     * 则无法保证找到的是哪一个。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *      插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *      则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
                                     Object key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            @SuppressWarnings("rawtypes")
            Comparable midVal = (Comparable)a[mid];
            @SuppressWarnings("unchecked")
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    /**
     * 使用二分搜索法来搜索指定数组,以获得指定对象。
     * 在进行此调用之前,必须根据指定的比较器(通过 sort(T[], Comparator) 方法)对数组进行升序排序。
     * 如果没有对数组进行排序,则结果是不确定的。如果数组包含多个等于指定对象的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param key   要搜索的值
     * @param c 用来对数组进行排序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   数组中对象的类数组中对象的类
     * @return 如果它包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:
     *      即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length。
     *      注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) {
        return binarySearch0(a, 0, a.length, key, c);
    }

    /**
     * 使用二分搜索法来搜索指定数组的范围,以获得指定对象。
     * 在进行此调用之前,必须根据指定的比较器(通过 sort(T[], int, int, Comparator) 方法)对范围进行升序排序。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个等于指定对象的元素,则无法保证找到的是哪一个。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @param c 用来对数组进行排序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   数组中对象的类
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *  插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *  则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    public static <T> int binarySearch(T[] a, int fromIndex, int toIndex,
                                       T key, Comparator<? super T> c) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key, c);
    }

    /**
     * 使用二分搜索法来搜索指定数组的范围,以获得指定对象。
     * 在进行此调用之前,必须根据指定的比较器(通过 sort(T[], int, int, Comparator) 方法)对范围进行升序排序。
     * 如果没有对范围进行排序,则结果是不确定的。如果范围包含多个等于指定对象的元素,则无法保证找到的是哪一个。
     *
     * 像公共版本,但没有范围检查。
     * @param a 要搜索的数组
     * @param fromIndex 要搜索的第一个元素的索引(包括)
     * @param toIndex   要搜索的最后一个元素的索引(不包括)
     * @param key   要搜索的值
     * @param c 用来对数组进行排序的比较器。null 值指示应该使用元素的自然顺序。
     * @param <T>   数组中对象的类
     * @return  如果它包含在数组的指定范围内,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
     *  插入点 被定义为将键插入数组的那一点:即范围中第一个大于此键的元素索引,如果范围中的所有元素都小于指定的键,
     *  则为 toIndex。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
     */
    private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
                                         T key, Comparator<? super T> c) {
        if (c == null) {
            return binarySearch0(a, fromIndex, toIndex, key);
        }
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            T midVal = a[mid];
            int cmp = c.compare(midVal, key);
            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // 找到关键字
        }
        return -(low + 1);  // 没有找到关键字
    }

    // 相等检验

    /**
     * 如果两个指定的 long 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(long[] a, long[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     * 如果两个指定的 int 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(int[] a, int[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     *  如果两个指定的 short 型数组彼此相等,则返回 true。
     *  如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     *  换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     *  此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(short[] a, short a2[]) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     * 如果两个指定的 char 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(char[] a, char[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     * 如果两个指定的 byte 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(byte[] a, byte[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     * 如果两个指定的 boolean 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(boolean[] a, boolean[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

    /**
     * 如果两个指定的 double 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     *
     * 如果以下条件成立,则认为两个 double 型数组 d1 和 d2 是相等的:
     *     new Double(d1).equals(new Double(d2))(与 == 操作符不同,此方法认为 NaN 等于它本身,而 0.0d 不等于 -0.0d。)
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(double[] a, double[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (Double.doubleToLongBits(a[i])!=Double.doubleToLongBits(a2[i])) // 位不相等
                return false;

        return true;
    }

    /**
     * 如果两个指定的 float 型数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * 如果以下条件成立,则认为两个 float 型数组 f1 和 f2 是相等的:
     *     new Float(f1).equals(new Float(f2))(与 == 操作符不同,此方法认为 NaN 等于它本身,而 0.0f 不等于 -0.0f。)
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(float[] a, float[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (Float.floatToIntBits(a[i])!=Float.floatToIntBits(a2[i])) // 位不相等
                return false;

        return true;
    }

    /**
     * 如果两个指定的 Objects 数组彼此相等,则返回 true。
     * 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。
     * 如果 (e1==null ? e2==null : e1.equals(e2)),则认为 e1 和 e2 这两个对象是相等的 。
     * 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
     * 此外,如果两个数组引用都为 null,则认为它们是相等的。
     * @param a 将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean equals(Object[] a, Object[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++) {
            Object o1 = a[i];
            Object o2 = a2[i];
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }

        return true;
    }

    // 填充

    /**
     * 将指定的 long 值分配给指定 long 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(long[] a, long val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 long 值分配给指定 long 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(long[] a, int fromIndex, int toIndex, long val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 int 值分配给指定 int 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(int[] a, int val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(int[] a, int fromIndex, int toIndex, int val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 short 值分配给指定 short 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(short[] a, short val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 short 值分配给指定 short 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(short[] a, int fromIndex, int toIndex, short val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 char 值分配给指定 char 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(char[] a, char val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 char 值分配给指定 char 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(char[] a, int fromIndex, int toIndex, char val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 byte 值分配给指定 byte 节型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(byte[] a, byte val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 byte 值分配给指定 byte 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(byte[] a, int fromIndex, int toIndex, byte val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 boolean 值分配给指定 boolean 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(boolean[] a, boolean val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 boolean 值分配给指定 boolean 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(boolean[] a, int fromIndex, int toIndex,
                            boolean val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 double 值分配给指定 double 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(double[] a, double val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 double 值分配给指定 double 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(double[] a, int fromIndex, int toIndex,double val){
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 float 值分配给指定 float 型数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(float[] a, float val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 float 值分配给指定 float 型数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(float[] a, int fromIndex, int toIndex, float val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    /**
     * 将指定的 Object 引用分配给指定 Object 数组的每个元素。
     * @param a 要填充的数组
     * @param val   要存储在数组所有元素中的值
     */
    public static void fill(Object[] a, Object val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

    /**
     * 将指定的 Object 引用分配给指定 Object 数组指定范围中的每个元素。
     * 填充的范围从索引 fromIndex(包括)一直到索引 toIndex(不包括)。(如果 fromIndex==toIndex,则填充范围为空。)
     * @param a 要填充的数组
     * @param fromIndex 要使用指定值填充的第一个元素的索引(包括)
     * @param toIndex   要使用指定值填充的最后一个元素的索引(不包括)
     * @param val   要存储在数组的所有元素中的值
     */
    public static void fill(Object[] a, int fromIndex, int toIndex, Object val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

    // 克隆

    /**
     * 复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 null。当且仅当指定长度大于原数组的长度时,这些索引存在。所得数组和原数组属于完全相同的类。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @param <T>   数组中对象的类
     * @return  原数组的副本,截取或用 null 填充以获得指定的长度
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

    /**
     * 复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 null。当且仅当指定长度大于原数组的长度时,这些索引存在。所得数组属于 newType 类。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @param newType   要返回的副本的类
     * @param <T>   返回数组中对象的类
     * @param <U>   原始数组中对象的类
     * @return 原数组的副本,截取或用 null 填充以获得指定的长度
     */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 (byte)0。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static byte[] copyOf(byte[] original, int newLength) {
        byte[] copy = new byte[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 (short)0。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static short[] copyOf(short[] original, int newLength) {
        short[] copy = new short[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 0。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original   要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 0L。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static long[] copyOf(long[] original, int newLength) {
        long[] copy = new long[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 '\\u000'。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 null 字符填充以获得指定的长度
     */
    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 0f。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return  原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static float[] copyOf(float[] original, int newLength) {
        float[] copy = new float[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 0d。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return 原数组的副本,截取或用 0 填充以获得指定的长度
     */
    public static double[] copyOf(double[] original, int newLength) {
        double[] copy = new double[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 复制指定的数组,截取或用 false 填充(如有必要),以使副本具有指定的长度。
     * 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,
     * 副本将包含 false。当且仅当指定长度大于原数组的长度时,这些索引存在。
     * @param original  要复制的数组
     * @param newLength 要返回的副本的长度
     * @return  原数组的副本,截取或用 false 元素填充以获得指定的长度
     */
    public static boolean[] copyOf(boolean[] original, int newLength) {
        boolean[] copy = new boolean[newLength];
        // 从original数组复制一个copy数组,复制从0开始,到copy数组的Math.min(original.length, newLength)结束。
        System.arraycopy(original, 0, copy, 0,
                Math.min(original.length, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)
     * (必须大于等于 from)可以大于 original.length,在这种情况下,
     * null 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * 所得数组与原数组属于完全相同的类。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @param <T>   数组中对象的类
     * @return 包含取自原数组指定范围的新数组,截取或用 null 填充以获得所需长度
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] copyOfRange(T[] original, int from, int to) {
        return copyOfRange(original, from, to, (Class<? extends T[]>) original.getClass());
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)
     * (必须大于等于 from)可以大于 original.length,在这种情况下,
     * null 被放入索引大于等于 original.length - from 的副本的所有元素中。
     * 返回数组的长度为 to - from。所得数组属于 newType 类。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @param newType   要返回的副本的类
     * @param <T>   返回数组中对象的类
     * @param <U>   原始数组中对象的类
     * @return 包含取自原数组指定范围的新数组,截取或用 null 填充以获得所需长度
     */
    public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to) (必须大于等于 from)可以大于 original.length,
     * 在这种情况下,(byte)0 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static byte[] copyOfRange(byte[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0)  // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        byte[] copy = new byte[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,
     * 在这种情况下,(short)0 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static short[] copyOfRange(short[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        short[] copy = new short[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,
     * 在这种情况下,0 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return  包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static int[] copyOfRange(int[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        int[] copy = new int[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,
     * 在这种情况下,>0L 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static long[] copyOfRange(long[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        long[] copy = new long[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,
     * 在这种情况下,'\\u000' 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        char[] copy = new char[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)
     * (必须大于等于 from)可以大于 original.length,在这种情况下,
     * 0f 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static float[] copyOfRange(float[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0)  // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        float[] copy = new float[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to)(必须大于等于 from)可以大于 original.length,
     * 在这种情况下,0d 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可能位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 0 填充以获得所需长度
     */
    public static double[] copyOfRange(double[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        double[] copy = new double[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 将指定数组的指定范围复制到一个新数组。
     * 该范围的初始索引 (from) 必须位于 0 和 original.length(包括)之间。
     * original[from] 处的值放入副本的初始元素中(除非 from == original.length 或 from == to)。
     * 原数组中后续元素的值放入副本的后续元素。该范围的最后索引 (to) (必须大于等于 from)可以大于 original.length,
     * 在这种情况下,false 被放入索引大于等于 original.length - from 的副本的所有元素中。返回数组的长度为 to - from。
     *
     * 参数:
     * @param original  将要从其复制一个范围的数组
     * @param from  要复制的范围的初始索引(包括)
     * @param to    要复制的范围的最后索引(不包括)。(此索引可能位于数组范围之外)。
     * @return 包含取自原数组指定范围的新数组,截取或用 false 元素填充以获得所需长度
     */
    public static boolean[] copyOfRange(boolean[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0) // 如果 from > to
            throw new IllegalArgumentException(from + " > " + to);
        boolean[] copy = new boolean[newLength];
        // 从original数组复制一个copy数组,复制从from开始,到copy数组的Math.min(original.length - from, newLength)结束。
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * 返回一个受指定数组支持的固定大小的列表。
     * (对返回列表的更改会“直接写”到数组。)此方法同 Collection.toArray() 一起,
     * 充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。返回的列表是可序列化的,并且实现了 RandomAccess。
     * 此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素:
     *
     *      List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
     * @param a 支持列表的数组。
     * @param <T>   数组中对象的类
     * @return 指定数组的列表视图。
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new java.util.Arrays.ArrayList<>(a);
    }

    /**
     * @serial include  包括
     * @param <E>
     */
    private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return java.util.Arrays.copyOf(this.a, size,
                        (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            java.util.Arrays.sort(a, c);
        }
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 long 型数组 a 和 b,也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Long 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return  a 数组基于内容的哈希码
     */
    public static int hashCode(long a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的非 null int 型数组 a 和 b,
     * 也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Integer 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(int a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (int element : a)
            result = 31 * result + element;

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 short 型数组 a 和 b,也可用说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Short 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return  a 数组基于内容的哈希码
     */
    public static int hashCode(short a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (short element : a)
            result = 31 * result + element;

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 char 型数组 a 和 b,也可用说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Character 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(char a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (char element : a)
            result = 31 * result + element;

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 byte 型数组 a 和 b,也可用说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Byte 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(byte a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (byte element : a)
            result = 31 * result + element;

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 boolean 型数组 a 和 b,也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Boolean 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(boolean a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (boolean element : a)
            result = 31 * result + (element ? 1231 : 1237);

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 float 型数组 a 和 b,也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Float 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(float a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (float element : a)
            result = 31 * result + Float.floatToIntBits(element);

        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 对于任何两个满足 Arrays.equals(a, b) 的 double 型数组 a 和 b,也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     * 此方法返回的值与在 List 上调用 hashCode 方法获得的值相同,
     * 该 List 包含以相同顺序表示 a 数组元素的 Double 实例的序列。如果 a 为 null,则此方法返回 0。
     * @param a 要计算其哈希值的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(double a[]) {
        if (a == null)
            return 0;

        int result = 1;
        for (double element : a) {
            long bits = Double.doubleToLongBits(element);
            result = 31 * result + (int)(bits ^ (bits >>> 32));
        }
        return result;
    }

    /**
     * 基于指定数组的内容返回哈希码。
     * 如果数组包含作为元素的其他数组,则哈希码将基于其标识,而不是基于其内容。
     * 所以,在将自身包含为一个元素的数组上,直接或间接通过一个或多个数组级别来调用此方法是可接受的。
     * 对于任何两个满足 Arrays.equals(a, b) 的数组 a 和 b,也可以说 Arrays.hashCode(a) == Arrays.hashCode(b)。
     *
     * 此方法返回的值等于 Arrays.asList(a).hashCode() 返回的值,除非 a 为 null,在这种情况下返回 0。
     * @param a  将计算其基于内容的哈希码的数组
     * @return a 数组基于内容的哈希码
     */
    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

    /**
     * 基于指定数组的“深层内容”返回哈希码。
     * 如果数组包含作为元素的其他数组,则哈希码将基于其内容,并以此类推,直至无穷。所以,在将自身包含为一个元素的数组上,
     * 直接或间接通过一个或多个数组级别来调用此方法是不可接受的。这种调用的行为是不确定的。
     * 对于任何两个满足 Arrays.deepEquals(a, b) 的数组 a 和 b,也可以说 Arrays.deepHashCode(a) == Arrays.deepHashCode(b)。
     *
     * 对此方法返回值的计算类似于对列表上的 List.hashCode() 返回值的计算,该列表以相同的顺序包含与 a 数组相同的元素,
     * 但有一点不同:如果数组 a 的 e 元素本身是一个数组,则不能通过调用 e.hashCode() 计算其哈希码,但是,
     * 如果 e 是一个基本类型数组,则可以通过调用 Arrays.hashCode(e) 的适当重载来计算其哈希码,或者,
     * 如果 e 是一个引用类型数组,则可以通过递归调用 Arrays.deepHashCode(e) 来计算其哈希码。
     * 如果 a 为 null,则此方法返回 0。
     * @param a 将计算其基于深层内容的哈希码的数组
     * @return a 数组基于深层内容的哈希码
     */
    public static int deepHashCode(Object a[]) {
        if (a == null) // a为空
            return 0;

        int result = 1;

        for (Object element : a) {
            int elementHash = 0;
            if (element instanceof Object[])
                elementHash = deepHashCode((Object[]) element);
            else if (element instanceof byte[])
                elementHash = hashCode((byte[]) element);
            else if (element instanceof short[])
                elementHash = hashCode((short[]) element);
            else if (element instanceof int[])
                elementHash = hashCode((int[]) element);
            else if (element instanceof long[])
                elementHash = hashCode((long[]) element);
            else if (element instanceof char[])
                elementHash = hashCode((char[]) element);
            else if (element instanceof float[])
                elementHash = hashCode((float[]) element);
            else if (element instanceof double[])
                elementHash = hashCode((double[]) element);
            else if (element instanceof boolean[])
                elementHash = hashCode((boolean[]) element);
            else if (element != null)
                elementHash = element.hashCode();

            result = 31 * result + elementHash;
        }

        return result;
    }

    /**
     * 如果两个指定数组彼此是深层相等 的,则返回 true。
     * 与 equals(Object[],Object[]) 方法不同,此方法适用于任意深度的嵌套数组。
     * 如果两个数组引用均为 null,或者它们引用了包含相同元素数量的数组,并且两个数组中的所有相应元素对都是深层相等的,
     * 则认为这两个数组引用是深层相等的。
     *
     * 如果满足以下任意条件之一,则两个 null 元素 e1 和 e2 可能是深层相等的:
     *
     * e1 和 e2 都是对象引用类型的数组,并且 Arrays.deepEquals(e1, e2) 将返回 true。
     * e1 和 e2 都是相同基本类型的数组,并且 Arrays.equals(e1, e2) 的适当重载将返回 true。
     * e1 == e2
     * e1.equals(e2) 将返回 true。
     * 注意,此定义支持任意深度的 null 元素。
     * 如果指定数组中的任意一个数组,直接或间接通过一个或多个数组级别,包含数组本身作为其元素,则此方法的行为是不确定的。
     * @param a1    将测试其相等性的一个数组
     * @param a2    将测试其相等性的另一个数组
     * @return 如果两个数组相等,则返回 true
     */
    public static boolean deepEquals(Object[] a1, Object[] a2) {
        if (a1 == a2)
            return true;
        if (a1 == null || a2==null)
            return false;
        int length = a1.length;
        if (a2.length != length)
            return false;

        for (int i = 0; i < length; i++) {
            Object e1 = a1[i];
            Object e2 = a2[i];

            if (e1 == e2)
                continue;
            if (e1 == null)
                return false;

            // 找出这两个元素是否相等
            boolean eq = deepEquals0(e1, e2);

            if (!eq)
                return false;
        }
        return true;
    }

    /**
     *
     * @param e1
     * @param e2
     * @return
     */
    static boolean deepEquals0(Object e1, Object e2) {
        assert e1 != null;
        boolean eq;
        if (e1 instanceof Object[] && e2 instanceof Object[])
            eq = deepEquals ((Object[]) e1, (Object[]) e2);
        else if (e1 instanceof byte[] && e2 instanceof byte[])
            eq = equals((byte[]) e1, (byte[]) e2);
        else if (e1 instanceof short[] && e2 instanceof short[])
            eq = equals((short[]) e1, (short[]) e2);
        else if (e1 instanceof int[] && e2 instanceof int[])
            eq = equals((int[]) e1, (int[]) e2);
        else if (e1 instanceof long[] && e2 instanceof long[])
            eq = equals((long[]) e1, (long[]) e2);
        else if (e1 instanceof char[] && e2 instanceof char[])
            eq = equals((char[]) e1, (char[]) e2);
        else if (e1 instanceof float[] && e2 instanceof float[])
            eq = equals((float[]) e1, (float[]) e2);
        else if (e1 instanceof double[] && e2 instanceof double[])
            eq = equals((double[]) e1, (double[]) e2);
        else if (e1 instanceof boolean[] && e2 instanceof boolean[])
            eq = equals((boolean[]) e1, (boolean[]) e2);
        else
            eq = e1.equals(e2);
        return eq;
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(long) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(long[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(int) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(int[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(short) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(short[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(char) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return  a 的字符串表示形式
     */
    public static String toString(char[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(byte) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return  a 的字符串表示形式
     */
    public static String toString(byte[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(boolean) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(boolean[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(float) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(float[] a) {
        if (a == null)
            return "null";

        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(double) 转换为字符串。如果 a 为 null,则返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(double[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组内容的字符串表示形式。
     * 如果数组包含作为元素的其他数组,则通过从 Object 中继承的 Object.toString() 方法将它们转换为字符串,
     * 这描述了它们的标识,而不是它们的内容。
     * 此方法返回的值等于 Arrays.asList(a).toString() 返回的值,除非 a 为 null,在这种情况下返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String toString(Object[] a) {
        if (a == null)
            return "null";

        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(String.valueOf(a[i]));
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    /**
     * 返回指定数组“深层内容”的字符串表示形式。
     * 如果数组包含作为元素的其他数组,则字符串表示形式包含其内容等。此方法是为了将多维数组转换为字符串而设计的。
     * 字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符 ", "(逗号加空格)分隔。
     * 这些元素通过 String.valueOf(Object) 转换为字符串,除非它们是自身的数组。
     *
     * 如果元素 e 是一个基本类型的数组,则通过调用 Arrays.toString(e) 的适当重载将它转换为字符串。
     * 如果元素 e 是一个引用类型的数组,则通过递归调用此方法将它转换为字符串。
     *
     * 为了避免无限递归,如果指定数组包含本身作为其元素,或者包含通过一个或多个数组级别对其自身的间接引用,
     * 则将自引用转换为字符串 "[...]"。例如,只包含对自身进行引用的数组将呈现为 "[[...]]"。
     *
     * 如果指定数组为 null,则此方法返回 "null"。
     * @param a 返回其字符串表示形式的数组
     * @return a 的字符串表示形式
     */
    public static String deepToString(Object[] a) {
        if (a == null)
            return "null";

        int bufLen = 20 * a.length;
        if (a.length != 0 && bufLen <= 0)
            bufLen = Integer.MAX_VALUE;
        StringBuilder buf = new StringBuilder(bufLen);
        deepToString(a, buf, new HashSet<Object[]>());
        return buf.toString();
    }

    /**
     *
     * @param a 返回其字符串表示形式的数组
     * @param buf   StringBuilder对象,用于组合字符串
     * @param dejaVu    HashSet<Object[]>()对象
     */
    private static void deepToString(Object[] a, StringBuilder buf,
                                     Set<Object[]> dejaVu) {
        if (a == null) {
            buf.append("null");
            return;
        }
        int iMax = a.length - 1;
        if (iMax == -1) {
            buf.append("[]");
            return;
        }

        dejaVu.add(a);
        buf.append('[');
        for (int i = 0; ; i++) {

            Object element = a[i];
            if (element == null) {
                buf.append("null");
            } else {
                Class<?> eClass = element.getClass();

                if (eClass.isArray()) {
                    if (eClass == byte[].class)
                        buf.append(toString((byte[]) element));
                    else if (eClass == short[].class)
                        buf.append(toString((short[]) element));
                    else if (eClass == int[].class)
                        buf.append(toString((int[]) element));
                    else if (eClass == long[].class)
                        buf.append(toString((long[]) element));
                    else if (eClass == char[].class)
                        buf.append(toString((char[]) element));
                    else if (eClass == float[].class)
                        buf.append(toString((float[]) element));
                    else if (eClass == double[].class)
                        buf.append(toString((double[]) element));
                    else if (eClass == boolean[].class)
                        buf.append(toString((boolean[]) element));
                    else { // 元素是对象引用的数组
                        if (dejaVu.contains(element))
                            buf.append("[...]");
                        else
                            deepToString((Object[])element, buf, dejaVu);
                    }
                } else {  // 元素是非空的,不是数组
                    buf.append(element.toString());
                }
            }
            if (i == iMax)
                break;
            buf.append(", ");
        }
        buf.append(']');
        dejaVu.remove(a);
    }

    /**
     * 设置指定数组的所有元素,使用提供的生成器函数计算每个元素。
     *
     * 如果生成器函数抛出异常,它将被转发给调用者,数组将处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     * @param <T>   数组元素的类型
     */
    public static <T> void setAll(T[] array, IntFunction<? extends T> generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.apply(i);
    }

    /**
     * 使用提供的生成器函数并行地设置指定数组的所有元素来计算每个元素。
     *
     * 如果生成器函数抛出异常,则从parallelSetAll抛出未检查的异常,数组处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     * @param <T>   数组元素的类型
     */
    public static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator) {
        Objects.requireNonNull(generator);
        IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.apply(i); });
    }

    /**
     * 设置指定数组的所有元素,使用提供的生成器函数计算每个元素。
     *
     * 如果生成器函数抛出异常,它将被转发给调用者,数组将处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void setAll(int[] array, IntUnaryOperator generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsInt(i);
    }

    /**
     * 使用提供的生成器函数并行地设置指定数组的所有元素来计算每个元素。
     *
     * 如果生成器函数抛出异常,则从parallelSetAll抛出未检查的异常,数组处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void parallelSetAll(int[] array, IntUnaryOperator generator) {
        Objects.requireNonNull(generator);
        IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.applyAsInt(i); });
    }

    /**
     * 设置指定数组的所有元素,使用提供的生成器函数计算每个元素。
     *
     * 如果生成器函数抛出异常,它将被转发给调用者,数组将处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void setAll(long[] array, IntToLongFunction generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsLong(i);
    }

    /**
     * 使用提供的生成器函数并行地设置指定数组的所有元素来计算每个元素。
     *
     * 如果生成器函数抛出异常,则从parallelSetAll抛出未检查的异常,数组处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void parallelSetAll(long[] array, IntToLongFunction generator) {
        Objects.requireNonNull(generator);
        IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.applyAsLong(i); });
    }

    /**
     * 设置指定数组的所有元素,使用提供的生成器函数计算每个元素。
     *
     * 如果生成器函数抛出异常,它将被转发给调用者,数组将处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void setAll(double[] array, IntToDoubleFunction generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsDouble(i);
    }

    /**
     * 使用提供的生成器函数并行地设置指定数组的所有元素来计算每个元素。
     *
     * 如果生成器函数抛出异常,则从parallelSetAll抛出未检查的异常,数组处于不确定状态。
     * @param array 要初始化的数组
     * @param generator 接受索引并为该位置生成所需值的函数
     */
    public static void parallelSetAll(double[] array, IntToDoubleFunction generator) {
        Objects.requireNonNull(generator);
        IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.applyAsDouble(i); });
    }

    /**
     * 返回一个覆盖所有指定数组的Spliterator。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @param <T>   类型的元素
     * @return  数组元素的spliterator
     */
    public static <T> Spliterator<T> spliterator(T[] array) {
        return Spliterators.spliterator(array,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回一个Spliterator,该Spliterator覆盖指定数组的指定范围。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @param <T>   类型的元素
     * @return  数组元素的spliterator
     */
    public static <T> Spliterator<T> spliterator(T[] array, int startInclusive, int endExclusive) {
        return Spliterators.spliterator(array, startInclusive, endExclusive,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回一个包含所有指定数组的Spliterator.OfInt。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfInt spliterator(int[] array) {
        return Spliterators.spliterator(array,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回包含指定数组的指定范围的Spliterator.OfInt。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfInt spliterator(int[] array, int startInclusive, int endExclusive) {
        return Spliterators.spliterator(array, startInclusive, endExclusive,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回一个包含所有指定数组的Spliterator.OfLong。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfLong spliterator(long[] array) {
        return Spliterators.spliterator(array,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回包含指定数组的指定范围的Spliterator.OfLong。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfLong spliterator(long[] array, int startInclusive, int endExclusive) {
        return Spliterators.spliterator(array, startInclusive, endExclusive,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回一个包含所有指定数组的Spliterator.OfDouble。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfDouble spliterator(double[] array) {
        return Spliterators.spliterator(array,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回包含指定数组的指定范围的Spliterator.OfDouble。
     *
     * spliterator报告 Spliterator.SIZED,Spliterator.SUBSIZED, Spliterator.ORDERED,和 Spliterator.IMMUTABLE.
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return  数组元素的spliterator
     */
    public static Spliterator.OfDouble spliterator(double[] array, int startInclusive, int endExclusive) {
        return Spliterators.spliterator(array, startInclusive, endExclusive,
                Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }

    /**
     * 返回指定数组作为源的连续流。
     * @param array 数组,假定在使用期间未被修改
     * @param <T>   数组元素的类型
     * @return  数组的流
     */
    public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }

    /**
     * 返回指定数组的指定范围作为其源的连续流。
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @param <T>   数组元素的类型
     * @return  数组范围的流
     */
    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
        return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
    }

    /**
     * 返回具有指定数组的连续IntStream
     * @param array 数组,假定在使用期间未被修改
     * @return  数组的IntStream
     */
    public static IntStream stream(int[] array) {
        return stream(array, 0, array.length);
    }

    /**
     * 以指定数组的指定范围作为源,返回一个连续的IntStream。
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return  数组范围的IntStream
     */
    public static IntStream stream(int[] array, int startInclusive, int endExclusive) {
        return StreamSupport.intStream(spliterator(array, startInclusive, endExclusive), false);
    }

    /**
     * 返回指定数组作为源的连续LongStream。
     * @param array 数组,假定在使用期间未被修改
     * @return  数组的LongStream
     */
    public static LongStream stream(long[] array) {
        return stream(array, 0, array.length);
    }

    /**
     * 返回指定数组的指定范围作为其源的连续LongStream。
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return 数组范围的LongStream
     */
    public static LongStream stream(long[] array, int startInclusive, int endExclusive) {
        return StreamSupport.longStream(spliterator(array, startInclusive, endExclusive), false);
    }

    /**
     * 返回指定数组作为源的连续DoubleStream。
     * @param array 数组,假定在使用期间未被修改
     * @return  数组的DoubleStream
     */
    public static DoubleStream stream(double[] array) {
        return stream(array, 0, array.length);
    }

    /**
     * 以指定数组的指定范围为源,返回一个连续的DoubleStream。
     * @param array 数组,假定在使用期间未被修改
     * @param startInclusive    第一个要覆盖的索引,包括
     * @param endExclusive  指数紧接上一个要覆盖的指数
     * @return  数组范围的DoubleStream
     */
    public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) {
        return StreamSupport.doubleStream(spliterator(array, startInclusive, endExclusive), false);
    }
}