Java 常用工具類 Collections 原始碼分析

Love Lenka發表於2017-10-26

文章出處

文章出自:安卓進階學習指南

作者:shixinzhang

完稿日期:2017.10.25

CollectionsArrays 是 JDK 為我們提供的常用工具類,方便我們操作集合和陣列。

這次之所以總結這個,是因為在一次面試中被問到一個細節,回答地不太好,這裡補一下吧。

由於兩個都是工具類,我們就放在一起學習。

讀完本文你將瞭解:

Collections

Collections 從名字就可以看出來,是對集合的操作,它提供了一系列內部類集合,主要為以下型別:

  • 不可變集合
  • 同步的集合
  • 有型別檢查的集合
  • 空集合
  • 只含一個元素的集合

還提供了一系列很有用的靜態方法,主要包括以下功能:

  • 排序
  • 二分查詢
  • 反轉
  • 打亂
  • 交換元素位置
  • 複製
  • 求出集合中最小/大值

我們先來看內部類的實現。

提供的多種內部類

1.不可變集合

有時候我們拿到一部分資料,這個資料不允許修改、刪除,只允許讀取,我們可以使用 Collections.unmodifiableXXX() 方法來實現。

Collections 提供了對以下集合的不可變支援:

這裡寫圖片描述

我們選幾個看一下原始碼。

首先看下最大範圍的 Collections.unmodifiableCollection(c) 方法,它可以返回一個容器的包裝類,這個包裝類的新增、替換、刪除操作都會丟擲異常 UnsupportedOperationException

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
    return new UnmodifiableCollection<>(c);
}

//一個不可變的集合
static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 1820017752578914078L;

    final Collection<? extends E> c;

    UnmodifiableCollection(Collection<? extends E> c) {
        if (c==null)
            throw new NullPointerException();
        this.c = c;
    }

    //普通查詢操作都是支援的,沒什麼特別
    public int size()                   {return c.size();}
    public boolean isEmpty()            {return c.isEmpty();}
    public boolean contains(Object o)   {return c.contains(o);}
    public Object[] toArray()           {return c.toArray();}
    public <T> T[] toArray(T[] a)       {return c.toArray(a);}
    public String toString()            {return c.toString();}

    public Iterator<E> iterator() {
        return new Iterator<E>() {
            private final Iterator<? extends E> i = c.iterator();

            public boolean hasNext() {return i.hasNext();}
            public E next()          {return i.next();}
            public void remove() {    //迭代器的 remove 也不支援
                throw new UnsupportedOperationException();
            }
            @Override
            public void forEachRemaining(Consumer<? super E> action) {
                // Use backing collection version
                i.forEachRemaining(action);
            }
        };
    }

    //這裡開始一系列修改操作就不支援了
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    public boolean remove(Object o) {
        throw new UnsupportedOperationException();
    }

    public boolean containsAll(Collection<?> coll) {
        return c.containsAll(coll);
    }
    public boolean addAll(Collection<? extends E> coll) {
        throw new UnsupportedOperationException();
    }
    public boolean removeAll(Collection<?> coll) {
        throw new UnsupportedOperationException();
    }
    public boolean retainAll(Collection<?> coll) {
        throw new UnsupportedOperationException();
    }
    public void clear() {
        throw new UnsupportedOperationException();
    }

    // Override default methods in Collection
    @Override
    public void forEach(Consumer<? super E> action) {
        c.forEach(action);
    }

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        throw new UnsupportedOperationException();
    }
}

剩下的也基本一樣了,再看個 Collections.unmodifiableList() 吧:

public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return (list instanceof RandomAccess ?
            new UnmodifiableRandomAccessList<>(list) :
            new UnmodifiableList<>(list));
}

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                              implements List<E> {
    private static final long serialVersionUID = -283967356065247728L;

    final List<? extends E> list;

    UnmodifiableList(List<? extends E> list) {
        super(list);
        this.list = list;
    }

    public boolean equals(Object o) {return o == this || list.equals(o);}
    public int hashCode()           {return list.hashCode();}

    public E get(int index) {return list.get(index);}
    public E set(int index, E element) {    //不支援
        throw new UnsupportedOperationException();
    }
    public void add(int index, E element) { //不支援
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {//不支援
        throw new UnsupportedOperationException();
    }
    public int indexOf(Object o)            {return list.indexOf(o);}
    public int lastIndexOf(Object o)        {return list.lastIndexOf(o);}
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void sort(Comparator<? super E> c) {
        throw new UnsupportedOperationException();
    }
    public ListIterator<E> listIterator()   {return listIterator(0);}

    public ListIterator<E> listIterator(final int index) {
        return new ListIterator<E>() {
            private final ListIterator<? extends E> i
                = list.listIterator(index);

            public boolean hasNext()     {return i.hasNext();}
            public E next()              {return i.next();}
            public boolean hasPrevious() {return i.hasPrevious();}
            public E previous()          {return i.previous();}
            public int nextIndex()       {return i.nextIndex();}
            public int previousIndex()   {return i.previousIndex();}

            public void remove() {
                throw new UnsupportedOperationException();
            }
            public void set(E e) {
                throw new UnsupportedOperationException();
            }
            public void add(E e) {
                throw new UnsupportedOperationException();
            }

            @Override
            public void forEachRemaining(Consumer<? super E> action) {
                i.forEachRemaining(action);
            }
        };
    }

    public List<E> subList(int fromIndex, int toIndex) {
        return new UnmodifiableList<>(list.subList(fromIndex, toIndex));
    }
}

其他型別的不可變集合也基本是這樣的,用一個包裝器類,持有實際集合類的引用,只對外提供查詢的方法,增刪改的通通拋異常。

2.同步的集合

如果你需要將一個集合的所有操作都設定為執行緒安全的,Collections.synchronizedXXX() 是一種方法。

Collections 提供了對以下集合的同步化支援:

這裡寫圖片描述

我們選幾個典型的看看實現。

public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
}

//第二個引數是用於同步的物件
static <T> Collection<T> synchronizedCollection(Collection<T> c, Object mutex) {
    return new SynchronizedCollection<>(c, mutex);
}

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 3053995032091335093L;

    final Collection<E> c;  // 實際的集合
    final Object mutex;     // 同步的物件

    SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
    }

    SynchronizedCollection(Collection<E> c, Object mutex) {
        this.c = Objects.requireNonNull(c);
        this.mutex = Objects.requireNonNull(mutex);
    }

    public int size() {
        synchronized (mutex) {return c.size();}
    }
    public boolean isEmpty() {
        synchronized (mutex) {return c.isEmpty();}
    }
    public boolean contains(Object o) {
        synchronized (mutex) {return c.contains(o);}
    }
    public Object[] toArray() {
        synchronized (mutex) {return c.toArray();}
    }
    public <T> T[] toArray(T[] a) {
        synchronized (mutex) {return c.toArray(a);}
    }

    public Iterator<E> iterator() {
        return c.iterator(); // 迭代器沒有進行同步操作,需要使用者自己同步
    }

    public boolean add(E e) {
        synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}
    }

    public boolean containsAll(Collection<?> coll) {
        synchronized (mutex) {return c.containsAll(coll);}
    }
    public boolean addAll(Collection<? extends E> coll) {
        synchronized (mutex) {return c.addAll(coll);}
    }
    public boolean removeAll(Collection<?> coll) {
        synchronized (mutex) {return c.removeAll(coll);}
    }
    public boolean retainAll(Collection<?> coll) {
        synchronized (mutex) {return c.retainAll(coll);}
    }
    public void clear() {
        synchronized (mutex) {c.clear();}
    }
    public String toString() {
        synchronized (mutex) {return c.toString();}
    }
    // Override default methods in Collection
    @Override
    public void forEach(Consumer<? super E> consumer) {
        synchronized (mutex) {c.forEach(consumer);}
    }
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        synchronized (mutex) {return c.removeIf(filter);}
    }
    @Override
    public Spliterator<E> spliterator() {
        return c.spliterator(); // Must be manually synched by user!
    }
    @Override
    public Stream<E> stream() {
        return c.stream(); // Must be manually synched by user!
    }
    @Override
    public Stream<E> parallelStream() {
        return c.parallelStream(); // Must be manually synched by user!
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        synchronized (mutex) {s.defaultWriteObject();}
    }
}

可以看到實現很簡單,幾乎在所有方法上都新增了 synchronized,這樣得到的集合在併發環境中效率可是大打折扣。要是對準確率要求比效能高,可以用用。

不過需要注意的是,我們在使用 Collections.synchronizedCollection(c) 返回的迭代器時,需要手動對迭代器進行同步,比如這樣:

Collection c = Collections.synchronizedCollection(myCollection);
  ...
  synchronized (c) {
     Iterator i = c.iterator(); // Must be in the synchronized block
     while (i.hasNext())
        foo(i.next());
  }

否則在迭代時有併發操作,可能會導致不確定性問題。

其他的同步集合也都類似,暴力的使用了 synchronized,沒什麼特別的,就不贅述了。

3.有型別檢查的集合

日常開發中我們經常需要使用 Object 作為返回值,然後在具體的使用處強轉成指定的型別,在這個強轉的過程中,編譯器無法檢測出強轉是否成功。

這時我們就可以使用 Collections.checkedXXX(c, type) 方法建立一個只儲存某個型別資料的集合,在新增時會進行型別檢查操作。

雖然使用泛型也可以實現在編譯時檢查,但在執行時,泛型擦除成 Object,最終還是需要強轉。

Collections 提供了對以下集合的型別檢查:

這裡寫圖片描述

Collections.checkedCollection(c, type) 方法提供了操作所有集合的基本操作,我們直接看它如何實現的就好了:

public static <E> Collection<E> checkedCollection(Collection<E> c,
                                                  Class<E> type) {
    return new CheckedCollection<>(c, type);
}

@SuppressWarnings("unchecked")
static <T> T[] zeroLengthArray(Class<T> type) {
    return (T[]) Array.newInstance(type, 0);
}

/**
 * @serial include
 */
static class CheckedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 1578914078182001775L;

    final Collection<E> c;
    final Class<E> type;    //目標型別

    void typeCheck(Object o) {
        if (o != null && !type.isInstance(o))    //檢查是否和目標型別一致
            throw new ClassCastException(badElementMsg(o));
    }

    private String badElementMsg(Object o) {
        return "Attempt to insert " + o.getClass() +
            " element into collection with element type " + type;
    }

    CheckedCollection(Collection<E> c, Class<E> type) {
        if (c==null || type == null)
            throw new NullPointerException();
        this.c = c;
        this.type = type;
    }

    public int size()                 { return c.size(); }
    public boolean isEmpty()          { return c.isEmpty(); }
    public boolean contains(Object o) { return c.contains(o); }
    public Object[] toArray()         { return c.toArray(); }
    public <T> T[] toArray(T[] a)     { return c.toArray(a); }
    public String toString()          { return c.toString(); }
    public boolean remove(Object o)   { return c.remove(o); }
    public void clear()               {        c.clear(); }

    public boolean containsAll(Collection<?> coll) {
        return c.containsAll(coll);
    }
    public boolean removeAll(Collection<?> coll) {
        return c.removeAll(coll);
    }
    public boolean retainAll(Collection<?> coll) {
        return c.retainAll(coll);
    }

    public Iterator<E> iterator() {
        // JDK-6363904 - unwrapped iterator could be typecast to
        // ListIterator with unsafe set()
        final Iterator<E> it = c.iterator();
        return new Iterator<E>() {
            public boolean hasNext() { return it.hasNext(); }
            public E next()          { return it.next(); }
            public void remove()     {        it.remove(); }};
        // Android-note: Should we delegate to it for forEachRemaining ?
    }

    public boolean add(E e) {    //新增時呼叫了型別檢查
        typeCheck(e);
        return c.add(e);
    }

    private E[] zeroLengthElementArray = null; // Lazily initialized

    private E[] zeroLengthElementArray() {
        return zeroLengthElementArray != null ? zeroLengthElementArray :
            (zeroLengthElementArray = zeroLengthArray(type));
    }

    public boolean addAll(Collection<? extends E> coll) {
        //這個方法可以讓我們避免併發時內容改變導致的問題
        return c.addAll(checkedCopyOf(coll));
    }

    @SuppressWarnings("unchecked")
    //檢查集合中的每個元素的型別
    Collection<E> checkedCopyOf(Collection<? extends E> coll) {
        Object[] a = null;
        try {
            E[] z = zeroLengthElementArray();
            a = coll.toArray(z);
            // Defend against coll violating the toArray contract
            if (a.getClass() != z.getClass())
                a = Arrays.copyOf(a, a.length, z.getClass());
        } catch (ArrayStoreException ignore) {
            // To get better and consistent diagnostics,
            // we call typeCheck explicitly on each element.
            // We call clone() to defend against coll retaining a
            // reference to the returned array and storing a bad
            // element into it after it has been type checked.
            //
            a = coll.toArray().clone();
            for (Object o : a)
                typeCheck(o);
        }
        // A slight abuse of the type system, but safe here.
        return (Collection<E>) Arrays.asList(a);
    }

    // Override default methods in Collection
    @Override
    public void forEach(Consumer<? super E> action) {c.forEach(action);}
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        return c.removeIf(filter);
    }
    @Override
    public Spliterator<E> spliterator() {return c.spliterator();}
    @Override
    public Stream<E> stream()           {return c.stream();}
    @Override
    public Stream<E> parallelStream()   {return c.parallelStream();}

可以看到,只是在 add(e) addAll() 時進行了型別檢查而已,不符合目標型別就會丟擲 ClassCastException 異常。

4.空集合

目前我還沒想出什麼時候需要建立一個空集合,看 Collections.emptyList() 的原始碼,建立的空列表除了內容為 0,還不支援新增操作,也就是永遠就是個空的。

何必呢?不懂啊。

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

/**
 * @serial include
 */
private static class EmptyList<E>
    extends AbstractList<E>
    implements RandomAccess, Serializable {
    private static final long serialVersionUID = 8842843931221139166L;

    public Iterator<E> iterator() {
        return emptyIterator();
    }
    public ListIterator<E> listIterator() {
        return emptyListIterator();
    }

    public int size() {return 0;}
    public boolean isEmpty() {return true;}

    public boolean contains(Object obj) {return false;}
    public boolean containsAll(Collection<?> c) { return c.isEmpty(); }

    public Object[] toArray() { return new Object[0]; }

    public <T> T[] toArray(T[] a) {
        if (a.length > 0)
            a[0] = null;
        return a;
    }

    public E get(int index) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }

    public boolean equals(Object o) {
        return (o instanceof List) && ((List<?>)o).isEmpty();
    }

    public int hashCode() { return 1; }

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        return false;
    }

    // Override default methods in Collection
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
    }

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

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
    }
    @Override
    public void sort(Comparator<? super E> c) {
    }


    // Preserves singleton property
    private Object readResolve() {
        return EMPTY_LIST;
    }
}

5.只含一個元素的集合

這個很好理解,有時候第三方方法需要的引數是一個 Map,而我們只有一組 key-value 資料怎麼辦,可以使用 Collections.singletonMap(key, value) 建立一個 Map。

我們來看下它的原始碼:

public static <K,V> Map<K,V> singletonMap(K key, V value) {
    return new SingletonMap<>(key, value);
}

private static class SingletonMap<K,V>
      extends AbstractMap<K,V>
      implements Serializable {
    private static final long serialVersionUID = -6979724477215052911L;

    private final K k;
    private final V v;

    //建構函式中將引數的 k-v 儲存到唯一的 k 和 v 中
    SingletonMap(K key, V value) {
        k = key;
        v = value;
    }

    public int size()                          {return 1;}

    public boolean isEmpty()                   {return false;}

    public boolean containsKey(Object key)     {return eq(key, k);}

    public boolean containsValue(Object value) {return eq(value, v);}

    public V get(Object key)                   {return (eq(key, k) ? v : null);}

    private transient Set<K> keySet = null;
    private transient Set<Map.Entry<K,V>> entrySet = null;
    private transient Collection<V> values = null;

    public Set<K> keySet() {
        if (keySet==null)
            keySet = singleton(k);
        return keySet;
    }

    public Set<Map.Entry<K,V>> entrySet() {
        if (entrySet==null)
            entrySet = Collections.<Map.Entry<K,V>>singleton(
                new SimpleImmutableEntry<>(k, v));
        return entrySet;
    }

    public Collection<V> values() {
        if (values==null)
            values = singleton(v);
        return values;
    }

    // Override default methods in Map
    @Override
    public V getOrDefault(Object key, V defaultValue) {
        return eq(key, k) ? v : defaultValue;
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        action.accept(k, v);
    }

    @Override
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V putIfAbsent(K key, V value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object key, Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V replace(K key, V value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V computeIfAbsent(K key,
                             Function<? super K, ? extends V> mappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V computeIfPresent(K key,
                              BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V compute(K key,
                     BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V merge(K key, V value,
                   BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }
}

這個比較簡單,比我們自己建立一個 Map 然後把資料放進去簡單些。

注意返回的 map 不支援刪除、替換操作。

Collections 支援建立單例的其他集合型別:

這裡寫圖片描述

提供的多種集合操作方法

排序

Collections 中提供了按自然排序 Comparable 進行比較的方法 sort(list):

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    if (list.getClass() == ArrayList.class) {    //
        Arrays.sort(((ArrayList) list).elementData, 0, list.size());
        return;
    }

    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

可以看到如果是 ArrayList 就直接呼叫 Arrays.sort(((ArrayList) list).elementData, 0, list.size()),因為 ArrayList 的底層實現就是陣列嘛;否則先呼叫 Arrays.sort(a),然後遍歷、更新。

我們去看一下 Arrays 對陣列的排序方法:

public static void sort(Object[] a, int fromIndex, int toIndex) {
    rangeCheck(a.length, fromIndex, toIndex);
    if (LegacyMergeSort.userRequested)
        legacyMergeSort(a, fromIndex, toIndex);
    else
        ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
}
static final class LegacyMergeSort {
    // Android-changed: Never use circular merge sort.
    private static final boolean userRequested = false;
}

透過註釋可以看到,Android 中不使用傳統的歸併排序。那我們看 ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0):

static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
    assert 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) {    // MIN_MERGE = 32
        int initRunLen = countRunAndMakeAscending(a, lo, hi);
        binarySort(a, lo, hi, lo + initRunLen);
        return;
    }

    //... 略去複雜的 TimSort 實現
}

可以看到,如果元素太少,會使用 二分插入排序 binarySort

binarySort 的思想是二分、將後續的數插入之前的已排序陣列。
binarySort 對陣列 a[lo:hi] 進行排序,並且a[lo:start] 是已經排好序的。
演算法的思路是對a[start:hi] 中的元素,每次使用binarySearch 為它在 a[lo:start] 中找到相應位置,並插入。

如果元素太多就會使用 TimSort,這個演算法是一種起源於歸併排序和插入排序的混合排序演算法,由 Tim Peters 於2002 年在 Python 語言中提出。

TimSort 的實現比較複雜,我們這裡簡單瞭解一下即可:

TimSort 的思想是先對待排序列進行分割槽,然後再對分割槽進行合併,看起來和歸併排序很相似,但是其中有一些針對反向和大規模資料的最佳化處理。

Collections 還提供了一種帶有定製排序引數的排序方法:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    if (list.getClass() == ArrayList.class) {
        Arrays.sort(((ArrayList) list).elementData, 0, list.size(), (Comparator) c);
        return;
    }

    Object[] a = list.toArray();
    Arrays.sort(a, (Comparator)c);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

和不傳入定製排序比較器使用的演算法是一樣的,區別就在於比較大小時的標準不一樣。

二分查詢

我們都知道二分查詢需要資料是有序的,在有序的情況下它時間複雜度平均為 O(logn)。

但是在 Collections.binarySearch() 的實現中,針對查詢列表的不同型別,採用了不同的查詢方法:

public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)    //BINARYSEARCH_THRESHOLD   = 5000;
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
}

如果這個 list 支援隨機訪問(比如 ArrayList),就呼叫 indexedBinarySearch() 方法,否則呼叫 iteratorBinarySearch(),我們挨個看看。

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) {
        int mid = (low + high) >>> 1;    //無符號右移 1 位,等於除二
        Comparable<? super T> midVal = list.get(mid);    //這個 get 方法時間複雜度為 O(1)
        int cmp = midVal.compareTo(key);

        if (cmp < 0)
            low = mid + 1;
        else if (cmp > 0)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found
}

由於支援隨機訪問,所以在其中的獲取方法時間複雜度很低。

相反看下 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);    //關鍵在於這個 get)()
        int cmp = midVal.compareTo(key);

        if (cmp < 0)
            low = mid + 1;
        else if (cmp > 0)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found
}

private static <T> T get(ListIterator<? extends T> i, int index) {
    T obj = null;
    int pos = i.nextIndex();
    if (pos <= index) {
        do {
            obj = i.next();
        } while (pos++ < index);
    } else {
        do {
            obj = i.previous();
        } while (--pos > index);
    }
    return obj;
}

可以看到,不支援隨機訪問的 List(比如連結串列 LinkedList)在二分查詢時,每次獲取元素都需要去遍歷迭代器,這樣就大大降低了效率,時間複雜度可能會達到 O(n) 及以上。

反轉列表

如果讓你實現一個反轉列表的方法你會怎麼寫?據說有的公司出過這樣的面試題。

來看看 Collections.reverse(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 {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        ListIterator fwd = list.listIterator();
        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);
        }
    }
}
public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

可以看到,當 List 支援隨機訪問時,可以直接從頭開始,第一個元素和最後一個元素交換位置,一直交換到中間位置。

這裡的這個 swap() 交換方法寫的很簡練哈,這裡簡單解釋一下:

  • l.get(i) 返回位置 i 上的元素
  • l.set(j,l.get(i)) 將 i 上的元素設定給 j,同時由於 List.set(i,E) 返回這個位置上之前的元素,所以可以返回原來在 j 上的元素
  • 然後再設定給 i

我們需要對集合的方法很熟悉才能寫出這樣的程式碼。

話說回不支援隨機訪問的列表,就使用兩個迭代器,一個從頭開始一個從尾開始,遍歷、賦值。

還是比較簡單的哈。

打亂列表中的元素

這個應該很容易實現的,只要隨機交換元素的位置就好了。看看是不是這麼實現的呢:

public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)
        r = rnd = new Random(); // harmless race.
    shuffle(list, rnd);
}
public static void shuffle(List<?> list, Random rnd) {
    int size = list.size();
    if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
        for (int i=size; i>1; i--)    //支援隨機訪問,就隨機賦值
            swap(list, i-1, rnd.nextInt(i));
    } else {
        Object arr[] = list.toArray();    //不支援隨機訪問,就轉成陣列

        // Shuffle array
        for (int i=size; i>1; i--)    //然後遍歷陣列交換
            swap(arr, i-1, rnd.nextInt(i));

        ListIterator it = list.listIterator();
        for (int i=0; i<arr.length; i++) {    //把打亂的元素寫回列表
            it.next();
            it.set(arr[i]);
        }
    }
}

可以看到,在打亂列表時能否隨機訪問的列表使用的方式也略有不同。支援隨機訪問的就直接隨機交換即可。

不支援隨機訪問的,在這種需要對全部元素做操作的情況下就比較尷尬了。上面的程式碼先將列表轉成了陣列,我們看看不支援隨機訪問的典型代表 LinkedList 如何實現的 toArray() 方法:

public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

辛酸淚啊,得遍歷一次。

轉成陣列後先進行了隨機交換,然後又把打亂的元素寫回列表,折騰死了哦。

填充列表

Collections 還有個 fill() 方法,表示使用指定的元素替換列表中的所有元素,也就是“你們都滾蛋,讓我複製 N 個”。

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);
        }
    }
}

實現很簡單,介紹它是因為好奇什麼時候會有這個需求?

求出集合中最小/大值

求最大最小值很簡單,這裡就不說了,就是提醒大家 Collections 有這些個方法:

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;
}
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
    if (comp==null)
        return (T)max((Collection) coll);

    Iterator<? extends T> i = coll.iterator();
    T candidate = i.next();

    while (i.hasNext()) {
        T next = i.next();
        if (comp.compare(next, candidate) > 0)
            candidate = next;
    }
    return candidate;
}

查詢子列表最早出現的索引

又是一道典型的面試題,你會怎麼解呢?

我們來看看 Collections 類給出的方法吧:

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開始
            for (int i=0, j=candidate; i<targetSize; i++, j++)
                if (!eq(target.get(i), source.get(j)))
                    continue nextCand;  // Element mismatch, try next cand
            return candidate;  // All elements of candidate matched target
        }

先看支援隨機訪問的(比如 ArrayList)。
這裡的程式碼使用了暴力的雙重迴圈,第一層從 0 開始,最多查詢 maxCandidate(父列表和子列表個數差距)次;
第二層開始,將第一層遍歷的索引範圍內,父列表和子列表對應位置的元素挨個對比,一旦有一個不相等,就回到第一層迴圈,繼續往後走一個。

直到第二層順利迴圈結束,就找到了子列表的索引。

其實思想就是:用兩個遊標,一個表示父列表的第一個元素,另一個表示可能是子列表元素範圍的索引,在遍歷過程中如果發現的確和子列表所有元素都相等,那就找到了子列表。

話說回來不支援隨機訪問的是如何找到子列表的呢?

    } else {  // Iterator version of above algorithm
        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())) {
                    // Back up source iterator to next candidate
                    for (int j=0; j<i; j++)
                        si.previous();
                    continue nextCand;
                }
            }
            return candidate;
        }
    }
    return -1;  // No candidate matched the target
}

思想有些類似,不同的是在不相等時,會往前移動一位指示目標列表位置的索引。

總結

可以看到 Collections 方法提供了很多有用的工具方法,其中有一部分也涉及到一些演算法,經過這篇文章你是不是更瞭解了呢?

Thanks

http://blog.csdn.net/bruce_6/article/details/38299199

相關文章