Java 的容器是 Java 語言中很重要的一部分,平常寫代碼會大量用到各類容器。Java 中的容器有一個龐大的體系,糾纏於細節很難全面掌握。這篇文章就總覽一下 Java 的容器,而後再深刻到細節中學習。java
Java 中的容器主要分爲兩部分,Collection 和 Map 兩種。Collection 主要用於存儲單個的元素。而 Map 則主要是存儲鍵值對。算法
本文基於 JDK1.8編程
上圖中圓圈表明接口, 長方形表明類,包括抽象類和普通類。綠色表明線程安全,黃色表明不是線程安全。上面的類圖中只包括了 java.util
下的類,java.util.concurrent
下面的容器類從功能的角度上來講並無太大不一樣,可是這個包下的類都是線程安全的。數組
從類圖中能夠看到 Collection 繼承了 Iterator 接口,說明全部的 Collection 均可以經過迭代器來進行訪問。安全
Collection 接口有三個子接口,List
、Set
和 Queue
。List 會按照元素的插入順序保存元素,Set 中的元素都不能重複。Collection 中定義了一些公共的方法,都是一些基礎的工具方法,好比獲取容器的大小、判斷容器時候爲空、清空容器、迭代容器元素等方法。在 JDK1.8 之後,在 Collection 接口中加入了 default 方法,這些方法都是用於支持 Java8 的函數式編程。微信
interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
boolean add(E e);
boolean remove(Object o);
boolean containsAll(java.util.Collection<?> c);
boolean addAll(java.util.Collection<? extends E> c);
boolean removeAll(java.util.Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
boolean retainAll(java.util.Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
複製代碼
List
接口下的 ArrayList 平常寫代碼使用的不少。ArrayList 的部分代碼以下。從代碼中能夠看到,ArrayList 底層的數據結構就是一個數組,並且 ArrayList 實現了 RandomAccess 來支持隨機訪問。數據結構
class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
transient Object[] elementData;
}
複製代碼
ArrayList 與數組的功能很像,可是提供了更多便利的操做。Vector 與 ArrayList 的功能基本一致,可是是線程安全的,Vector 的子類 Stack 一樣也是線程安全的,可是這些類基本都不推薦再使用了。若是要使用線程安全的類,java.util.concurrent 中的 CopyOnWriteArrayList
是一種更好的選擇。多線程
LinkedList 與 ArrayList 功能也比較相近,從功能的角度上來講,它們之間最大的區別在於 ArrayList 支持隨機訪問,而 LinkedList 則不支持。LinkedList 部分代碼以下,能夠看到 LinkedList 底層使用的是雙向鏈表的數據結構。並且還實現了 Deque 接口,因此除了能夠做爲列表容器來使用以外,還能夠做爲隊列或者雙端隊列來使用。併發
class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}
複製代碼
LinkedList 一樣在 java.util.concurrent 中提供 LinkedBlockingQueue 和 LinkedBlockingDeque 來實現一樣的功能,除了在多線程環境比 LinkedList 更有優點外,功能方面基本沒有差異。app
各種 Set
的共同點在於 set 的元素是不重複的,這一特性在一些狀況下很是有用,HashSet 是用的最多的 Set 類。如下是 HashSet 的部分代碼,比較有意思的是 HashSet 底層是使用 HashMap 實現的,全部的值都存着在 HashMap 的 Key 中,Value 的位置就放一個固定的對象 PRESENT。
class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
}
複製代碼
HashSet 裏面的元素是無序的,若是須要讓 set 中元素有序,那麼就可使用 LinkedHashSet,LinkedHashSet 中經過構造一個雙向鏈表來記錄插入順序。而 TreeSet 則是經過底層的紅黑樹結構提供了排序順序的訪問方式,具體用哪一種能夠看具體的需求。一樣 Set 也有線程安全的版本 CopyOnWriteArraySet
。
Queue/Deque
是 Java 中的提供的 隊列接口。ArrayQueue 是具體可使用的隊列類,能夠做爲普通隊列或則雙端隊列來使用。可是隊列在併發狀況使用的更多一點,使用 LinkedBlockingQueue 或者 LinkedBlockingDeque 會是更好的選擇。有時候除了順序隊列以外,可能還須要經過優先級來調度的隊列,PriorityQueue 就是爲這個需求而生的,在併發狀況下與之對應的就是 PriorityBlockingQueue。
Map 的類圖結構相對來講就簡單不少。全部的 Map 類都繼承了 Map 接口。HashMap 是使用的最多的 Map 類,HashMap 也是無序的,和 Set 相似,LinkedHashMap 和 TreeMap 也從不一樣的方面保證順序,LinkedHashMap 經過雙向鏈表來記錄插入順序。TreeMap 則是對其中的元素進行排序,能夠按照排序的順序進行訪問。
做爲 Map 的典型實現,HashMap 代碼結構就複雜的多,HashMap 號稱是有着 的訪問速度(只是近似,在極端狀況下可能退化成
)。這麼快速的關鍵在於哈希函數的實現,哈希函數好的實現能夠幫助鍵值對均勻的分佈,從而有
的訪問速度,如下是 HashMap 的哈希函數的實現,並且 HashMap 的擴容和處理哈希碰撞等問題的處理也很複雜。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼
與 Collection 中的結構相似,HashTable 也與 HashMap 功能相似,可是 HashTable 是線程安全的。一樣由於 HashTable 實現的方式不如 java.util.concurrent 中提供的性能好,因此不推薦使用 HashTable。在併發狀況下推薦使用 ConcurrentHashMap,ConcurrentHashMap 經過分段鎖的機制,在併發狀況下也能有較好的性能。若是在併發狀況下也須要保證 Map 的順序,那就使用 ConcurrentNavigableMap。
在 java.util 包下有一個 Collections 類,這是一個工具類,裏面全部的方法都是靜態的,並且類不能被實例化。裏面提供了各類方法,能夠用來更有效率的操做各種容器對象。
好比對 List 排序:
ArrayList<Integer> list = new ArrayList();
list.add(1);
list.add(4);
list.add(6);
list.add(2);
list.add(8);
Collections.sort(list);
複製代碼
固然還能夠自定義排序的規則,本身實現一個 Comparator
而後做爲參數傳入就行了。
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : 0;
}
});
複製代碼
還有開箱即用的二分查找算法:
Collections.binarySearch(list, 2);
複製代碼
還能夠直接把 list 進行反轉:
Collections.reverse(list);
複製代碼
還能夠把 list 使用洗牌算法打亂:
Collections.shuffle(list);
複製代碼
以上只是其中的一部分方法,還有能夠交換 list 中的元素,找出 list 中的最小、最大值等方法。
由於 java.util 包下的容器大部分都不是線程安全的,Collections 有一類方法能夠把 普通的容器對象轉成線程安全的對象:
Collections.synchronizedList(list);
複製代碼
對於 Map 和 Set 也有相似的工具方法。
在併發環境下,還能夠把一個普通容器對象轉化成一個不可變的容器對象,這樣在併發環境下也是線程安全的:
Collections.unmodifiableList(list);
複製代碼
(完)
關注微信公衆號,聊點其餘的