【集合框架】JDK1.8原始碼分析之Collections && Arrays(十)

leesf發表於2016-03-26

一、前言

  整個集合框架的常用類我們已經分析完成了,但是還有兩個工具類我們還沒有進行分析。可以說,這兩個工具類對於我們操作集合時相當有用,下面進行分析。

二、Collections原始碼分析

  2.1 類的屬性 

public class Collections {
    // 二分查詢閾值
    private static final int BINARYSEARCH_THRESHOLD   = 5000;
    // 反向閾值
    private static final int REVERSE_THRESHOLD        =   18;
    // 洗牌閾值
    private static final int SHUFFLE_THRESHOLD        =    5;
    // 填充閾值
    private static final int FILL_THRESHOLD           =   25;
    // 旋轉閾值
    private static final int ROTATE_THRESHOLD         =  100;
    // 拷貝閾值
    private static final int COPY_THRESHOLD           =   10;
    // 替換閾值
    private static final int REPLACEALL_THRESHOLD     =   11;
    // 子集合索引閾值
    private static final int INDEXOFSUBLIST_THRESHOLD =   35;
}
View Code

  2.2 建構函式  

private Collections() {   
}    

  說明:私有建構函式,在類外無法呼叫。

  2.3 方法分析

  下面是Collections的所有方法。

  

  

  可以看到,Collections的方法包含了各種各樣的操作。下面分析最常用的方法。

  1. sort函式

  該函式有兩個過載函式,方法簽名分別如下

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

  說明:對於第一個函式,引數為List<T> list,表示只能對List進行排序。由<T extends Comparable<? super T>>可知,T型別或者是T的父型別必須實現了Comparable介面。對於第二個函式,也包含了List<T> list引數,還包含了Comparator<? super T> c,表示可以指定自定義比較器Comparator。而元素型別不需要實現Comparable介面。兩個函式都會呼叫到List類的sort方法。具體如下 

default void sort(Comparator<? super E> c) {
        // 轉化為陣列
        Object[] a = this.toArray();
        // 呼叫Arrays類的sort函式
        Arrays.sort(a, (Comparator) c);
        // 迭代器
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
View Code

  2. binarySearch函式

  該函式也有兩個過載版本,方法簽名分別如下  

public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)

  說明:與sort函式的兩個過載版本類似,可以指定自定義比較器,特別注意,使用此函式時,必須要保證List已經排好序,並且集合元素是可以比較的。其中,兩個binarySearch函式會呼叫indexedBinarySearch函式,具體函式如下  

private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;
        
        while (low <= high) {
            // 取low - high 的中間索引,直接使用移位操作,效率更高
            int mid = (low + high) >>> 1;
            // 取中間元素
            Comparable<? super T> midVal = list.get(mid);
            // 中間元素與key比較
            int cmp = midVal.compareTo(key);

            if (cmp < 0) // 小於key,在高半部分查詢
                low = mid + 1;
            else if (cmp > 0) // 大於key,在低半部分查詢
                high = mid - 1;
            else // 找到key,返回位置
                return mid; // key found
        }
        return -(low + 1);  // 沒有找到key,返回負數
    }
View Code

  說明:該函式在List可以隨機訪問時被呼叫,效率相比iteratorBinarySearch更高,當List不能被隨機訪問時,將採用iteratorBinarySearch進行二分查詢,即採用迭代器模式。iteratorBinarySearch具體程式碼如下  

private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        // 獲取迭代器
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();
        // 迴圈控制
        while (low <= high) {
            // 取得中間索引
            int mid = (low + high) >>> 1;
            // 得到中間元素
            Comparable<? super T> midVal = get(i, mid);
            // 中間元素與key比較
            int cmp = midVal.compareTo(key);
            if (cmp < 0) // 小於key,在高半部分查詢
                low = mid + 1;
            else if (cmp > 0) // 大於key,在低半部分查詢
                high = mid - 1;
            else // 找到key,返回位置
                return mid; 
        }
        return -(low + 1);  // 沒有找到key,返回負數
    }
View Code

  說明:該函式會呼叫get函式,即遍歷集合找元素,所以效率相對較低。get函式如下  

private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        // 下一個結點索引
        int pos = i.nextIndex();
        if (pos <= index) { //下一個結點索引小於index,從前往後 
            do {
                obj = i.next();
            } while (pos++ < index);
        } else { // 下一個結點索引不小於index,從後往前
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        // 返回元素
        return obj;
    }
View Code

  3. reverse函式

  此函式用於反轉集合中的元素,其簽名如下

public static void reverse(List<?> list)

  具體程式碼如下

public static void reverse(List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) { // 小於反向閾值或者可以隨機訪問
            // 把元素從中間分隔為兩部分,交換兩部分的值
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--) 
                swap(list, i, j);
        } else { // 否則
            // 獲取從頭開始的迭代器
            ListIterator fwd = list.listIterator();
            // 獲取從size位置(從尾)開始的迭代器
            ListIterator rev = list.listIterator(size);
            // 從開始遍歷到中間位置
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                // 取下一元素
                Object tmp = fwd.next();
                // 交換元素
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }
View Code

  說明:若集合支援隨機訪問或者集合大小小於反轉閾值,則採用直接交換操作;否則,就會採用雙迭代器模式(從頭開始的,從尾開始的)進行交換。

  4. fill函式

  此函式用於給集合填充指定元素,簽名如下  

public static <T> void fill(List<? super T> list, T obj)

  泛型方法,具體程式碼如下

    public static <T> void fill(List<? super T> list, T obj) {
        int size = list.size();
    
        if (size < FILL_THRESHOLD || list instanceof RandomAccess) { // 小於填充閾值或者集合可以隨機訪問
            // 遍歷集合
            for (int i=0; i<size; i++)
                list.set(i, obj);
        } else { // 否則
            // 使用迭代器模式進行填充
            ListIterator<? super T> itr = list.listIterator();
            for (int i=0; i<size; i++) {
                itr.next();
                itr.set(obj);
            }
        }
    }
View Code

  說明:也是同reverse函式一樣,分為兩種情況處理。

  5. copy函式

  此函式用於拷貝集合,將源集合拷貝至目標集合,簽名如下

public static <T> void copy(List<? super T> dest, List<? extends T> src)

  其具體程式碼如下

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        // 源集合大小
        int srcSize = src.size();
        if (srcSize > dest.size()) // 源集合大小大於目標集合大小,丟擲異常
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) { // 小於拷貝閾值或者(src和dest集合都支援隨機訪問
            // 遍歷,拷貝
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else { // 否則
            // 目標集合的迭代器
            ListIterator<? super T> di=dest.listIterator();
            // 源集合的迭代器
            ListIterator<? extends T> si=src.listIterator();
            // 遍歷,拷貝
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }
View Code

  說明:也是分為兩種情況進行處理,並且要確保目標集合大小大於源集合大小。

  6. min函式

  此函式用於求得集合裡最小的元素,有兩個過載版本,簽名如下  

public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)

  說明:可以指定比較器,取出自定義的最小的元素。第一個函式具體程式碼如下

    public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) {
        // 獲取迭代器
        Iterator<? extends T> i = coll.iterator();
        // 第一個元素為候選元素
        T candidate = i.next();
        
        while (i.hasNext()) {
            // 下一個元素
            T next = i.next();
            // 下一個元素小於候選元素
            if (next.compareTo(candidate) < 0)
                // 改變候選元素
                candidate = next;
        }
        // 返回最小元素
        return candidate;
    }
View Code

  說明:只需要遍歷一遍集合即可取出最小值,另外一個過載函式類似,不再累贅,max函式與min函式類似,不再累贅。

  7. rotate函式

  此函式用於旋轉集合元素,實際就是迴圈右移集合裡的元素,簽名如下  

public static void rotate(List<?> list, int distance)

  集合元素迴圈右移,移動的距離為distance,具體程式碼如下

public static void rotate(List<?> list, int distance) {
        if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD) // 可隨機訪問或小於閾值
            rotate1(list, distance);
        else // 否則
            rotate2(list, distance);
    }
View Code

  說明:也分為兩種情況進行處理,分別對應rotate1、rotate2。roate1函式具體如下

    private static <T> void rotate1(List<T> list, int distance) {
        // 取得集合大小
        int size = list.size();
        if (size == 0) // 集合為空,返回
            return;
        distance = distance % size; // 取模操作
        if (distance < 0) // 為負數,之後保證為正數
            distance += size;
        if (distance == 0) // 移動距離為0,直接返回
            return;
        // 遍歷集合
        for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {
            T displaced = list.get(cycleStart);
            int i = cycleStart;
            do {
                i += distance;
                if (i >= size)
                    i -= size;
                displaced = list.set(i, displaced);
                nMoved ++;
            } while (i != cycleStart);
        }
    }
View Code

  說明:這個演算法特別的巧妙,可作為面試考點。rotate2函式具體如下  

    private static void rotate2(List<?> list, int distance) {
        int size = list.size();
        if (size == 0)
            return;
        int mid =  -distance % size;
        if (mid < 0)
            mid += size;
        if (mid == 0)
            return;
        // 將AB變為BA,可以先對A求逆,再對B求逆,再對整體求逆,則可以得到BA
        // 先反向0到mid的元素
        reverse(list.subList(0, mid));
        // 再反向mid到size的元素
        reverse(list.subList(mid, size));
        // 最後反向整個表
        reverse(list);        
    }
View Code

  說明:使用遞迴進行旋轉。

  8. replaceAll函式

  用於替換集合中所有指定元素。簽名如下  

public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)

  具體程式碼如下

public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) {
        boolean result = false;
        // 集合大小
        int size = list.size();
        if (size < REPLACEALL_THRESHOLD || list instanceof RandomAccess) { // 小於替換閾值或者可以隨機訪問
            if (oldVal==null) { // 舊值為空
                // 遍歷集合
                for (int i=0; i<size; i++) { 
                    if (list.get(i)==null) { // 為空就設定為新值
                        list.set(i, newVal);
                        result = true;
                    }
                }
            } else { // 舊值不為空
                // 遍歷集合
                for (int i=0; i<size; i++) {
                    if (oldVal.equals(list.get(i))) { // 與舊值相等就設定為新值
                        list.set(i, newVal);
                        result = true;
                    }
                }
            }
        } else { // 否則
            // 獲取迭代器
            ListIterator<T> itr=list.listIterator();
            if (oldVal==null) { // 舊值為空
                for (int i=0; i<size; i++) { // 遍歷
                    if (itr.next()==null) {
                        itr.set(newVal);
                        result = true;
                    }
                }
            } else { // 舊值不為空
                for (int i=0; i<size; i++) {
                    if (oldVal.equals(itr.next())) { // 與舊值相等就設定為新值
                        itr.set(newVal);
                        result = true;
                    }
                }
            }
        }
        return result;
    }
View Code

  說明:可以替換空值null。也是分兩種情況進行處理。

  9. indexOfSubList函式

  用於在指定集合索引子集合,成功,則返回位置,不成功,則返回-1。簽名如下 

public static int indexOfSubList(List<?> source, List<?> target)

  具體程式碼如下  

    public static int indexOfSubList(List<?> source, List<?> target) {
        // 源集合大小
        int sourceSize = source.size();
        // 目標集合大小
        int targetSize = target.size();
        // 大小差
        int maxCandidate = sourceSize - targetSize;
        
        if (sourceSize < INDEXOFSUBLIST_THRESHOLD ||
            (source instanceof RandomAccess&&target instanceof RandomAccess)) { // 小於子集索引閾值或者(源集合與目標集合都支援隨機訪問)
        nextCand:
            for (int candidate = 0; candidate <= maxCandidate; candidate++) { // 只需要遍歷從0到maxCandidate即可
                for (int i=0, j=candidate; i<targetSize; i++, j++)
                    if (!eq(target.get(i), source.get(j))) // 不相等
                        continue nextCand; // 不匹配,又回到for,此時將不會執行int candidate = 0操作
                return candidate; // 全部匹配,返回索引
            }
        } else { // 否則,使用迭代器操作
            // 獲取迭代器
            ListIterator<?> si = source.listIterator();
        nextCand:
            for (int candidate = 0; candidate <= maxCandidate; candidate++) {
                ListIterator<?> ti = target.listIterator();
                for (int i=0; i<targetSize; i++) {
                    if (!eq(ti.next(), si.next())) { // 不相等
                        // 回溯源集合迭代器
                        for (int j=0; j<i; j++)
                            si.previous();
                        continue nextCand; // 又回到for,此時將不會執行int candidate = 0操作
                    }
                }
                return candidate; // 全部匹配,返回索引
            }
        }
        return -1;  // 不匹配,返回-1
    }
View Code

  說明:也是分為兩種情況進行處理。

  10. frequency函式

  用來統計一個元素在集合中出現的次數。簽名如下  

public static int frequency(Collection<?> c, Object o)

  具體程式碼如下 

public static int frequency(Collection<?> c, Object o) {
        int result = 0;
        if (o == null) { // 物件為空
            // 遍歷集合
            for (Object e : c) 
                if (e == null) // 為空
                    result++;
        } else { // 物件不為空
            // 遍歷集合
            for (Object e : c)
                if (o.equals(e))
                    result++;
        }
        return result;
    }
View Code

  說明:可以對空值null進行統計。

  11. reverseOrder函式

  反轉比較邏輯,即反轉集合順序,有兩個過載函式,簽名如下  

public static <T> Comparator<T> reverseOrder()
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)

  函式返回型別為Comparator型別,該Comparator的比較邏輯與之前的比較邏輯相反。reverseOrder函式程式碼如下 

    public static <T> Comparator<T> reverseOrder() {
        return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
    }
View Code

  說明:另外一個過載函式與此類似,不再累贅。

  12. addAll函式

  用於向集合中新增多個元素,簽名如下  

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

  說明:第二個引數為變長引數,即可以傳遞多個值。具體程式碼如下 

    public static <T> boolean addAll(Collection<? super T> c, T... elements) {
        boolean result = false;
        for (T element : elements)
            result |= c.add(element);
        return result;
    }
View Code

  Collections的主要方法就分析到這裡。下面分析Arrays類的方法。

三、Arrays原始碼分析

  3.1 類的屬性  

public class Arrays {
    // 可以進行並行排序的最小陣列長度
    private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
}
View Code

  3.2 建構函式 

private Arrays() {}

  說明:私有建構函式,類外不允許呼叫。

  3.3 方法分析

  Arrays的全部方法如下

           

  說明:可以看到,Arrays工具類處理的是陣列型別。並且每個方法存在多個過載版本。

  3.4 核心方法分析

  由於存在多個過載版本,每個過載版本的邏輯大體一致,故只分析有代表性的版本。

  1. sort函式

  用於對陣列進行排序,兩個主要的過載版本,方法簽名如下  

public static void sort(int[] a)
public static void sort(int[] a, int fromIndex, int toIndex)

  說明:第一個版本是對整個陣列進行排序,第二個版本只對指定部分進行排序。第一個版本程式碼如下  

public static void sort(int[] a) {
        // 使用快速排序
        DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
    }
View Code

  說明:使用快速排序進行排序。第二個版本與第一個版本類似,不再累贅。

  2. equals方法

  用於判斷兩個陣列是否相等,若陣列元素重寫了equals方法,則按照元素的equals方法進行比較。主要的過載版本,方法簽名如下  

public static boolean equals(int[] a, int[] a2)
public static boolean equals(Object[] a, Object[] a2)

  說明:第一個版本具體程式碼如下 

public static boolean equals(int[] a, int[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null) // 任一陣列為空,則返回false
            return false;

        int length = a.length;
        if (a2.length != length) // 陣列大小不相等,返回false
            return false;

        for (int i=0; i<length; i++) // 遍歷比較,元素全部相等則返回true,否則,返回false
            if (a[i] != a2[i])
                return false;

        return true;
    }
View Code

  說明:首先,判斷是否為同一個陣列引用,滿足後,再判斷是否有陣列為空,滿足後,再判斷兩陣列長度是否相等,滿足後,最後遍歷陣列進行比較。

  3. copyOf函式

  用於複製陣列元素至另一個陣列。主要過載版本,簽名如下  

public static <T> T[] copyOf(T[] original, int newLength)
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)

  第二個版本,具體程式碼如下 

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);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
View Code

  4. copyOfRange函式

  指定陣列一段元素進行復制(指定了開始位置和結束位置),簽名如下  

public static <T> T[] copyOfRange(T[] original, int from, int to)
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)

  第二個版本,具體程式碼如下 

public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }
View Code

  5. hashCode函式

  求得陣列的hashCode,主要版本,簽名如下 

public static int hashCode(int 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;
    }
View Code

  6. asList函式

  用於將不定引數轉化為List,方法簽名如下  

public static <T> List<T> asList(T... a)

  具體程式碼如下  

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
View Code 

  7. toString函式

  用於更友好的顯示陣列資訊,簽名如下 

public static String toString(int[] a)

  注意,並沒有覆蓋Object類的toString方法,因為方法簽名不相同。具體程式碼如下 

    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(", ");
        }
    }
View Code

  Arrays工具類的主要方法就介紹到這裡,平時我們可以多用用裡面的方法,達到更熟悉的效果。

四、總結

  Collections與Arrays提供了很多有用的方法,我們平時可以多用用,至此,集合框架的主要原始碼就分析完了。下面會接著分析併發框架的原始碼,謝謝各位園友的觀看~

  

  

 

  

  

相關文章