下圖是 Java 中 Collection 相關的接口與類的關系的類圖。其中,類只是集合框架的一部分,比較常用的一部分。
第一次畫類圖,著實很費勁,不過收獲也不小。 下面是相關接口和類的解釋說明。文字來自 JDK API 1.6 中文版。原諒我的懶惰,實在不想自己寫,太麻煩。如有錯誤,還請指正。 如圖,Set、Queue、List 接口都繼承自 Collection 接口。AbstractCollection<E>
此類提供 Collection 接口的骨干實現,以最大限度地減少了實現此接口所需的工作。
要實現一個不可修改的 collection,編程人員只需擴展此類,並提供 iterator 和 size 方法的實現。(iterator 方法返回的迭代器必須實現 hasNext 和 next。)
要實現可修改的 collection,編程人員必須另外重寫此類的 add 方法(否則,會拋出 UnsupportedOperationException),iterator 方法返回的迭代器還必須另外實現其 remove 方法。
按照 Collection 接口規范中的建議,編程人員通常應提供一個 void (無參數)和 Collection 構造方法。
Set 一個不包含重復元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2,並且最多包含一個 null 元素。 注:如果將可變對象用作 set 元素,那麼必須極其小心。如果對象是 set 中某個元素,以一種影響 equals 比較的方式改變對象的值,那麼 set 的行為就是不確定的。此項禁止的一個特殊情況是不允許某個 set 包含其自身作為元素。 HashSet 此類實現 Set 接口,由哈希表(實際上是一個 HashMap 實例)支持。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。此類允許使用 null 元素。 此類為基本操作提供了穩定性能,這些基本操作包括 add、remove、contains 和 size,假定哈希函數將這些元素正確地分布在桶中。對此 set 進行迭代所需的時間與 HashSet 實例的大小(元素的數量)和底層 HashMap 實例(桶的數量)的“容量”的和成比例。因此,如果迭代性能很重要,則不要將初始容量設置得太高(或將加載因子設置得太低)。 注意,此實現不是同步的。如果多個線程同時訪問一個哈希 set,而其中至少一個線程修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在創建時完成這一操作,以防止對該 set 進行意外的不同步訪問:
Set s = Collections.synchronizedSet(new HashSet(...));注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步並發修改做出任何硬性保證。快速失敗迭代器在盡最大努力拋出 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤做法:迭代器的快速失敗行為應該僅用於檢測 bug。 LinkedHashSet 具有可預知迭代順序的 Set 接口的哈希表和鏈接列表實現。此實現與 HashSet 的不同之外在於,後者維護著一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,即按照將元素插入到 set 中的順序(插入順序)進行迭代。注意,插入順序不 受在 set 中重新插入的 元素的影響。(如果在 s.contains(e) 返回 true 後立即調用 s.add(e),則元素 e 會被重新插入到 set s 中。) 此類提供所有可選的 Set 操作,並且允許 null 元素。與 HashSet 一樣,它可以為基本操作(add、contains 和 remove)提供穩定的性能,假定哈希函數將元素正確地分布到存儲段中。由於增加了維護鏈接列表的開支,其性能很可能會比 HashSet 稍遜一籌,不過,這一點例外:LinkedHashSet 迭代所需時間與 set 的大小 成正比,而與容量無關。HashSet 迭代很可能支出較大,因為它所需迭代時間與其容量 成正比。 鏈接的哈希 set 有兩個影響其性能的參數:初始容量 和加載因子。它們與 HashSet 中的定義極其相同。注意,為初始容量選擇非常高的值對此類的影響比對 HashSet 要小,因為此類的迭代時間不受容量的影響。 注意,此實現不是同步的。如果多個線程同時訪問一個哈希 set,而其中至少一個線程修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在創建時完成這一操作,以防止對該 set 進行意外的不同步訪問:
Set s = Collections.synchronizedSet(new LinkedHashSet(...));注意,迭代器的快速失敗行為不能得到保證,一般來說,存在不同步的並發修改時,不可能作出任何強有力的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程序的方式是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用於檢測程序錯誤。 TreeSet 基於 TreeMap 的 NavigableSet 實現。使用元素的自然順序對元素進行排序,或者根據創建 set 時提供的 Comparator 進行排序,具體取決於使用的構造方法。 此實現為基本操作(add、remove 和 contains)提供受保證的 log(n) 時間開銷。 注意,如果要正確實現 Set 接口,則 set 維護的順序(無論是否提供了顯式比較器)必須與 equals 一致。(關於與 equals 一致 的精確定義,請參閱 Comparable 或 Comparator。)這是因為 Set 接口是按照 equals 操作定義的,但 TreeSet 實例使用它的 compareTo(或 compare)方法對所有元素進行比較,因此從 set 的觀點來看,此方法認為相等的兩個元素就是相等的。即使 set 的順序與 equals 不一致,其行為也是 定義良好的;它只是違背了 Set 接口的常規協定。 注意,此實現不是同步的。如果多個線程同時訪問一個 TreeSet,而其中至少一個線程修改了該 set,那麼它必須 外部同步。這一般是通過對自然封裝該 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSortedSet 方法來“包裝”該 set。此操作最好在創建時進行,以防止對 set 的意外非同步訪問:
Set s = Collections.synchronizedSet(new TreeSet(...));注意,迭代器的快速失敗行為無法得到保證,一般來說,存在不同步的並發修改時,不可能作出任何肯定的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用於檢測 bug。 CopyOnWriteArraySet 對其所有操作使用內部 CopyOnWriteArrayList 的 Set。因此,它共享以下相同的基本屬性: 1、它最適合於具有以下特征的應用程序:set 大小通常保持很小,只讀操作遠多於可變操作,需要在遍歷期間防止線程間的沖突。 2、它是線程安全的。 3、因為通常需要復制整個基礎數組,所以可變操作(add、set 和 remove 等等)的開銷很大。 4、迭代器不支持可變 remove 操作。 5、使用迭代器進行遍歷的速度很快,並且不會與其他線程發生沖突。在構造迭代器時,迭代器依賴於不變的數組快照。 ConcurrentSkipListSet 一個基於 ConcurrentSkipListMap 的可縮放並發 NavigableSet 實現。set 的元素可以根據它們的自然順序進行排序,也可以根據創建 set 時所提供的 Comparator 進行排序,具體取決於使用的構造方法。 此實現為 contains、add、remove 操作及其變體提供預期平均 log(n) 時間開銷。多個線程可以安全地並發執行插入、移除和訪問操作。迭代器是弱一致的,返回的元素將反映迭代器創建時或創建後某一時刻的 set 狀態。它們不拋出 ConcurrentModificationException,可以並發處理其他操作。升序排序視圖及其迭代器比降序排序視圖及其迭代器更快。 請注意,與在大多數 collection 中不同,這裡的 size 方法不是一個固定時間 (constant-time) 操作。由於這些 set 的異步特性,確定元素的當前數目需要遍歷元素。此外,批量操作 addAll、removeAll、retainAll 和 containsAll 並不 保證能以原子方式 (atomically) 執行。例如,與 addAll 操作一起並發操作的迭代器只能查看某些附加元素。 此類及其迭代器實現 Set 和 Iterator 接口的所有可選方法。與大多數其他並發 collection 實現一樣,此類不允許使用 null 元素,因為無法可靠地將 null 參數及返回值與不存在的元素區分開來。 Queue 在處理元素前用於保存元素的 collection。除了基本的 Collection 操作外,隊列還提供其他的插入、提取和檢查操作。每個方法都存在兩種形式:一種拋出異常(操作失敗時),另一種返回一個特殊值(null 或 false,具體取決於操作)。插入操作的後一種形式是用於專門為有容量限制的 Queue 實現設計的;在大多數實現中,插入操作不會失敗。 隊列通常(但並非一定)以 FIFO(先進先出)的方式排序各個元素。不過優先級隊列和 LIFO 隊列(或堆棧)例外,前者根據提供的比較器或元素的自然順序對元素進行排序,後者按 LIFO(後進先出)的方式對元素進行排序。無論使用哪種排序方式,隊列的頭 都是調用 remove() 或 poll() 所移除的元素。在 FIFO 隊列中,所有的新元素都插入隊列的末尾。其他種類的隊列可能使用不同的元素放置規則。每個 Queue 實現必須指定其順序屬性。 Queue 實現通常不允許插入 null 元素,盡管某些實現(如 LinkedList)並不禁止插入 null。即使在允許 null 的實現中,也不應該將 null 插入到 Queue 中,因為 null 也用作 poll 方法的一個特殊返回值,表明隊列不包含元素。 Queue 實現通常未定義 equals 和 hashCode 方法的基於元素的版本,而是從 Object 類繼承了基於身份的版本,因為對於具有相同元素但有不同排序屬性的隊列而言,基於元素的相等性並非總是定義良好的。 ConcurrentLinkedQueue 一個基於鏈接節點的無界線程安全隊列。此隊列按照 FIFO(先進先出)原則對元素進行排序。隊列的頭部 是隊列中時間最長的元素。隊列的尾部 是隊列中時間最短的元素。新的元素插入到隊列的尾部,隊列獲取操作從隊列頭部獲得元素。當多個線程共享訪問一個公共 collection 時,ConcurrentLinkedQueue 是一個恰當的選擇。此隊列不允許使用 null 元素。 內存一致性效果:當存在其他並發 collection 時,將對象放入 ConcurrentLinkedQueue 之前的線程中的操作 happen-before 隨後通過另一線程從 ConcurrentLinkedQueue 訪問或移除該元素的操作。 List 有序的 collection(也稱為序列)。此接口的用戶可以對列表中每個元素的插入位置進行精確地控制。用戶可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。 與 set 不同,列表通常允許重復的元素。更確切地講,列表通常允許滿足 e1.equals(e2) 的元素對 e1 和 e2,並且如果列表本身允許 null 元素的話,通常它們允許多個 null 元素。難免有人希望通過在用戶嘗試插入重復元素時拋出運行時異常的方法來禁止重復的列表,但我們希望這種用法越少越好。 注意:盡管列表允許把自身作為元素包含在內,但建議要特別小心:在這樣的列表上,equals 和 hashCode 方法不再是定義良好的。 ArrayList List 接口的大小可變數組的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。(此類大致上等同於 Vector 類,除了此類是不同步的。) size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間運行。add 操作以分攤的固定時間 運行,也就是說,添加 n 個元素需要 O(n) 時間。其他所有操作都以線性時間運行(大體上講)。與用於 LinkedList 實現的常數因子相比,此實現的常數因子較低。 每個 ArrayList 實例都有一個容量。該容量是指用來存儲列表元素的數組的大小。它總是至少等於列表的大小。隨著向 ArrayList 中不斷添加元素,其容量也自動增長。並未指定增長策略的細節,因為這不只是添加元素會帶來分攤固定時間開銷那樣簡單。 在添加大量元素前,應用程序可以使用 ensureCapacity 操作來增加 ArrayList 實例的容量。這可以減少遞增式再分配的數量。 注意,此實現不是同步的。 LinkedList List 接口的鏈接列表實現。實現所有可選的列表操作,並且允許所有元素(包括 null)。除了實現 List 接口外,LinkedList 類還為在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操作允許將鏈接列表用作堆棧、隊列或雙端隊列。 此類實現 Deque 接口,為 add、poll 提供先進先出隊列操作,以及其他堆棧和雙端隊列操作。 所有操作都是按照雙重鏈接列表的需要執行的。在列表中編索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。 注意,此實現不是同步的。 CopyOnWriteArrayList ArrayList 的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的復制來實現的。 這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更有效。在不能或不想進行同步遍歷,但又需要從並發線程中排除沖突時,它也很有用。“快照”風格的迭代器方法在創建迭代器時使用了對數組狀態的引用。此數組在迭代器的生存期內不會更改,因此不可能發生沖突,並且迭代器保證不會拋出 ConcurrentModificationException。創建迭代器以後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操作(remove、set 和 add)不受支持。這些方法將拋出 UnsupportedOperationException。 允許使用所有元素,包括 null。 內存一致性效果:當存在其他並發 collection 時,將對象放入 CopyOnWriteArrayList 之前的線程中的操作 happen-before 隨後通過另一線程從 CopyOnWriteArrayList 中訪問或移除該元素的操作。 Vector Vector 類可以實現可增長的對象數組。與數組一樣,它包含可以使用整數索引進行訪問的組件。但是,Vector 的大小可以根據需要增大或縮小,以適應創建 Vector 後進行添加或移除項的操作。 每個向量會試圖通過維護 capacity 和 capacityIncrement 來優化存儲管理。capacity 始終至少應與向量的大小相等;這個值通常比後者大些,因為隨著將組件添加到向量中,其存儲將按 capacityIncrement 的大小增加存儲塊。應用程序可以在插入大量組件前增加向量的容量;這樣就減少了增加的重分配的量。 Stack Stack 類表示後進先出(LIFO)的對象堆棧。它通過五個操作對類 Vector 進行了擴展 ,允許將向量視為堆棧。它提供了通常的 push 和 pop 操作,以及取堆棧頂點的 peek 方法、測試堆棧是否為空的 empty 方法、在堆棧中查找項並確定到堆棧頂距離的 search 方法。 首次創建堆棧時,它不包含項。 Deque 接口及其實現提供了 LIFO 堆棧操作的更完整和更一致的 set,應該優先使用此 set,而非此類。例如:
Deque<Integer> stack = new ArrayDeque<Integer>();