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!


                                                                                          謝謝閱讀                 ----知飛翀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章