Java Arrays、Collections、Math常用工具类源码算法解析

Java Arrays、Collections、Math常用工具类源码解析


Java SE中有自带的两个常用的工具类Arrays类(针对数据相关的处理)、Math类(针对数字计算相关的处理),由于Math类的函数大致与C++等编程语言相同,底层的介绍显得意义并不大,只要是学习过编程的程序猿想必对Maths中的函数并不陌生,故本篇博文将以Arrays类为重点,并着重介绍Arrays类中的sort方法的底层算法实现,而Math类的介绍将重点从使用的角度介绍以下常用的API即可。下面,嘿嘿!造作吧~~~~

温馨提示:源码的介绍中将会涉及到多种排序查找等算法,需具备一定的数据结构与算法基本功。如有不适请点击~数据结构与算法—数组~


Arrays篇

Arrays类结构

Arrays工具类的设计就是为了帮助开发者非常方便的使用Arrays类中的方法来处理数组对象。下面我将从Arrays类中最值得分析的binarySearch()方法与sort()方法入手展开。

binarySearch()方法分析

binarySearch()方法:从binary search不难看出这是一个用二分查找算法实现的方法,能快速定位出要查找数组中指定数值的具体位置,若不存在指定数值返回负值。源码如下图:

从以上两个图中可以看出,binarySearch方法中调用了binarySearch0()方法,我们阅读binarySearch0()方法的源码可以看出其使用的正是二分查找算法;由此可以看出binarySearch方法的执行效率还是可以的,但是不足之处正是二分查找的缺点:待查找的数组必须为有序数组

 

sort()方法分析

sort()方法:Arrays类中的sort方法用来进行数组的排序,这是一个综合性的算法实现方法,在sort()排序方法中融合了快排、选择排序等。sort方法对于基本数据类型(7种)采用归并排序、插入排序、快排算法、计数排序,而对于自定义类型则采用选择排序。进行这样区分的原因主要是不同类型的数据对稳定性的要求不同,基本数据类型只涉及数、字符的排序,不需要满足稳定性,于是选择更加快速的不稳定算法快排;而自定义类型数据需要保证稳定性,故不采用快排,而采用稳定性算法插入排序、归并排序

基本数据类型参数&源码清单:

     /*
     * Tuning parameters.
     */

    /**
     * The maximum number of runs in merge sort.归并排序
     */
    private static final int MAX_RUN_COUNT = 67;

    /**
     * The maximum length of run in merge sort.归并排序
     */
    private static final int MAX_RUN_LENGTH = 33;

    /**
     * If the length of an array to be sorted is less than this
     * constant, Quicksort is used in preference to merge sort.快排
     */
    private static final int QUICKSORT_THRESHOLD = 286;

    /**
     * If the length of an array to be sorted is less than this
     * constant, insertion sort is used in preference to Quicksort.插入排序
     */
    private static final int INSERTION_SORT_THRESHOLD = 47;

    /**
     * If the length of a byte array to be sorted is greater than this
     * constant, counting sort is used in preference to insertion sort.计数排序
     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29;

    /**
     * If the length of a short or char array to be sorted is greater
     * than this constant, counting sort is used in preference to Quicksort.计数排序
     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200;

 

从以上参数清单中不难看出,sort方法对于基本数据类型共采用了4种排序算法,这4种排序算法的选取是按照传入数组的取值范围和数组的长度来进行最优选取的;在从源码清单中可以看出在进行确切的排序算法之前都是先进行if条件判断数组取值或长度来选取特定的算法的。

 

自定义数据类型参数&源码清单:

对于自定义数据类型的sort排序方法,有两种选择①归并排序夹杂插入排序;②TimSort排序,这是归并排序与插入排序的结合体(Timsort.sort()方法是对归并排序算法的改进,主要从归并排序算法的归并阶段进行优化,在数据量较低时采用插入排序)。 

以下内容是方法①的源代码:其中INSERTIONSORT_THRESHOLD=7参数作为进行选择插入排序和归并排序的依据

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

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

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

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

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

以下内容是方法②的源代码:Timsort.sort()源码

     /**
     * Sorts the given range, using the given workspace array slice
     * for temp storage when possible. This method is designed to be
     * invoked from public methods (in class Arrays) after performing
     * any necessary array bounds checks and expanding parameters into
     * the required forms.
     *
     * @param a the array to be sorted
     * @param lo the index of the first element, inclusive, to be sorted
     * @param hi the index of the last element, exclusive, to be sorted
     * @param c the comparator to use
     * @param work a workspace array (slice)
     * @param workBase origin of usable space in work array
     * @param workLen usable size of work array
     * @since 1.8
     */
    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

Timsort算法详解:https://stackoverflow.com/questions/7770230/comparison-between-timsort-and-quicksort

Timsort与quicksort讨论:https://stackoverflow.com/questions/7770230/comparison-between-timsort-and-quicksort


Collections篇

Collections概述

Collections是JDK提供的工具类:

  • boolean addAll(Collection<? super T> c, T... elements)

创建空集合(不可变):

  • List<T> emptyList()
  • Map<K,V> emptyMap()
  • Set<T> emptySet()
  • ....等

创建单元素集合(不可变):

  • Set<T> singleton(T o)
  • List<T> singleton(T o)
  • Map<K,V> singletonMap(K key,V value)

对list排序(必须传入可变List):

  • void sort(List<T> list)
  • void sort(List<T> list,Comparator<? super T> c)

随机重置List的元素:

  • void shuffle(List<?> list)

把可变集合变为不可变集合:(非真正意义上的不可更改

  • List<T> unmodifiableList(List<? extends T> list)
  • Set<T> unmodifiableList(List<? extends T> set)
  • Map<K,V> unmodfiableMap(Map<? extends K, ? extends V> m)
  • 代码测试:

把线程不安全的集合变为线程安全的集合:(不推荐使用)

  • List<T> synchronizedList(List<T> list)
  • Set<T> synchronizedSet(Set<T> set)
  • Map<K,V> synchronizedMap(Map<K,V> m)

Collections类工具方法总结

  • 创建空集合
  • 创建单元素集合
  • 创建不可变集合(非真正意义上不可变)
  • 排序 / 洗牌

Arrays与Collections工具类的主要区别是处理的对象不同,Arrays是针对数组设计的,而Collections是针对集合设计的。Collections中主要介绍binarySearch()、sort()、reverse()、shuffle()这4个常用的方法,其他的主要方法清读者自行查看API。

binarySearch():依旧是用二分查找的算法,与Arrays中不同的是这里还涉及到另外以一种用迭代器来辅助查找,源码如下图:(如果对迭代器不熟悉可查看本博客下博文:迭代器)

sort():从Collecations源码中不难看出sort方法的底层实现就是借用Arrays类中的sort来实现的。下图为源码截图:

reverse():用来将集合逆序排序——反转集合,源码也比较容易理解,不要在意具体的细节。源码如下:

 

 shuffle():随机置换,将集合元素打散随机排序。源码如下:


Math篇

 Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。下面列出常用API

成员方法
    * public static int abs(int a)——取绝对值   
    * public static double ceil(double a)——向上取整
    * public static double floor(double a)——向下取整
    * public static int max(int a,int b)——取最大值
    * public static double pow(double a,double b)——a的b次方
    * public static double random()——取随机值
    * public static int round(float a) ——四舍五入取整
    * public static double sqrt(double a)——开平方                                                                                                                        * public static double sin(double a)——三角函数

这里值得注意的是用Math类中的方法操作的数值的范围,最特殊的点是最值(最大值、最小值);这也是本人在做算法题的时候发现的问题,先分享出来:(以int类型求abs为例)

从上图我们发现在abs下int的最小值min_value无法变成+2147483648,了解计算机原理的想必已近知道了其中的原因。因为int类型的值的范围在-2147483648~2147483647之间,也就无法取的范围以外的值了。具体的解释见下方(——选自stackoverflow

The integer value -1 is can be noted as 0xFFFFFFFF in hexadecimal in Java (check this out with a println or any other method). Taking -(-1) thus gives:

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

So, it works.

Let us try now with Integer.MIN_VALUE . Knowing that the lowest integer can be represented by 0x80000000, that is, the first bit set to 1 and the 31 remaining bits set to 0, we have:

-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE

And this is why Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE. Also note that 0x7FFFFFFF is Integer.MAX_VALUE.

End!


                                                                                          谢谢阅读                 ----知飞翀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值