Java 常用工具類 Collections 原始碼分析
文章出處
文章出自:安卓進階學習指南
作者:shixinzhang
完稿日期:2017.10.25
Collections
和 Arrays
是 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
相關文章
- Java Collections 原始碼分析2017-09-06Java原始碼
- java原始碼分析 Arrays.asList()與Collections.unmodifiableList()2020-02-12Java原始碼
- java的Collections中二分查詢原始碼分析2016-06-10Java原始碼
- Java —— 集合工具類(Collections 類)2018-10-23Java
- Java 集合類——Collections(1)2019-02-26Java
- Java 集合類——Collections(3)2019-03-10Java
- Java 集合類——Collections(2)2019-03-05Java
- Java之Collections工具類2020-10-11Java
- Java類集框架 —— ArrayList原始碼分析2019-01-16Java框架原始碼
- Java類集框架 —— HashMap原始碼分析2017-09-22Java框架HashMap原始碼
- Java容器類框架分析(5)HashSet原始碼分析2019-03-04Java框架原始碼
- Java容器類框架分析(1)ArrayList原始碼分析2019-01-28Java框架原始碼
- Java類集框架 —— LinkedList原始碼分析2018-12-21Java框架原始碼
- Java類集框架 —— LinkedHashMap原始碼分析2017-09-22Java框架HashMap原始碼
- Java容器類框架分析(2)LinkedList原始碼分析2019-01-21Java框架原始碼
- java 常用工具類2021-09-22Java
- java常用工具類2017-09-22Java
- Java類集框架 —— HashSet、LinkedHashSet原始碼分析2019-03-04Java框架原始碼
- 【集合框架】JDK1.8原始碼分析之Collections && Arrays(十)2016-03-26框架JDK原始碼
- Java集合類原始碼2024-06-16Java原始碼
- java 常用工具類 (值得收藏)2022-03-14Java
- java 常用工具類 方法整理2018-01-22Java
- Java常用工具類方法整理2024-11-05Java
- Collections工具類2020-11-01
- Java 的可變引數與 Collections 類2023-04-26Java
- java 原始碼分析 —Boolean2019-03-03Java原始碼Boolean
- Java 原始碼如何分析?2021-11-12Java原始碼
- Java:HashMap原始碼分析2018-03-11JavaHashMap原始碼
- 【Java原始碼】集合類-ArrayDeque2019-06-11Java原始碼
- Laravel 請求類原始碼分析2019-02-28Laravel原始碼
- JDK 原始碼分析(1) Object類2018-09-05JDK原始碼Object
- DRF之排序類原始碼分析2024-04-23排序原始碼
- DRF之分頁類原始碼分析2024-04-23原始碼
- java collections2015-03-04Java
- Collections工具類,可以使用collections工具類對程式碼中的list進行分組2024-03-23
- 技術乾貨:Java程式碼常用工具類整理歸納2019-11-15Java
- 6、Collections工具類2023-02-14
- Java容器原始碼學習--ArrayList原始碼分析2021-12-26Java原始碼