轉載請註明原創出處,謝謝!
Java 容器的繼承關係圖:
集合表示一組對象,稱爲其元素。有些集合允許重複元素,而另一些則不允許。有些是有序的,有些是無序的。 JDK 沒有提供這個接口的任何直接實現:它提供了更具體的子接口(如 Set、List 和 Queue)的實現。
一、List
有序的元素序列,可以通過索引(即下標)訪問元素。
1、ArrayList(基於索引的動態數組)
(1)實現了可變大小的數組,允許所有元素,包括 null 值,底層使用數組(array)保存所有元素,所以隨機訪問很快,可以直接通過元素的下標值獲取元素的值(size、isEmpty、get、set、iterator、listIterator 這些方法的時間複雜度均爲 O(1)),但插入和刪除較慢,因爲需要移動 array 裏的元素(即 add、remove 的時間複雜度爲 O(n)),未實現同步。
(2)每一個 ArrayList 實例都有一個容量(capacity),使用 Lists.newArrayList() 創建的是一個 capacity = 0
的 List,當在添加第一個元素的時候會擴展到默認的初始化容量(10),當對其添加的數據大於它的 capacity 就必須改變 ArrayList 的 capacity(一般是原來大小的 1.5 倍),而這種 resize 操作是有開銷的,所以如果事先知道數組的大小爲 actualSize,可以按照下面的方式初始化一個大小固定的 ArrayList,以減去 resize 的開銷:
int actualSize = 100;
List<Object> objectArrayList = Lists.newArrayListWithCapacity(actualSize);
(3)iterator() 和 listIterator(int) 返回的迭代器是快速失敗(fail-fast)的:
如果在迭代器創建之後,原始的 List 被修改了,迭代器會拋一個 ConcurrentModificationException,原因是 Iterator 裏的 expectedModCount 和 List 的 modCount 不一致。在迭代的時候如果需要修改 List,只能通過 Iterator 的 remove 方法修改。
(4)從 Array 創建 ArrayList 的坑:
Object[] array = new Object[10];
List<Object> arrayList1 = Lists.newArrayList(Arrays.asList(array));
List<Object> arrayList2 = Arrays.asList(array);
// 需要注意的是:Arrays.asList(array) 返回的是一個 fixed size array(上面的arrayList2),如果不用 Lists.newArrayList(Arrays.asList(array))(上面的arrayList2)包裝起來的話,對它進行 add 或 remove 操作就會報 java.lang.UnsupportedOperationException
2、LinkedList(雙鏈表數據結構)
(1)實現了 List 接口,允許 null 值,底層使用鏈表保存所有元素(除了要存數據外,還需存 next 和 pre 兩個指針,因此佔用的內存比 ArrayList 多),因此,向 LinkedList 裏面插入或移除元素時會特別快,但是對於隨機訪問方面相對較慢(需要遍歷鏈表,遍歷的時候會根據 index 選擇從前往後或從後往前遍歷,如果 index < (size >> 1) 則從前往後),無同步,想要實現同步可以這樣:
List list = Collections.synchronizedList(new LinkedList(...));
(2)LinkedList 還擁有了可以使其用作堆棧(stack),隊列(queue)或者雙向隊列(deque)的方法(擁有 pop、push,從 LinkedList 的首部或尾部添加或刪除元素等方法)。
3、Vector(實現了同步的 ArrayList)
和 ArrayList 幾乎一模一樣,除開以下兩點:
-
實現了同步,較 ArrayList 有輕微的性能上的差距(一般不用它,而是使用 ArrayList,在外部實現同步);
-
二者的 resize 的大小不一樣:ArrayList 是變爲原來的 1.5 倍,而 Vector 爲原來的 2 倍。
4、Stack(Java 的堆棧實現是糟糕的,它繼承了 Vector)
Stack 表示後進先出(LIFO)堆棧,繼承於 Vector ,新增了五個方法:
-
push(E item)
:將 item 壓入棧; -
pop()
:remove 掉棧頂元素並返回 remove 掉的元素; -
peek()
:返回棧頂(Vector 的最後一個元素)的第一個元素(無 remove 操作); -
empty()
:判斷棧是否爲空; -
search()
:返回查找到的離棧頂最近的元素的 position; -
javadoc 中建議:Deque 接口和它的實現提供了一個更完整、更一致的 LIFO 棧操作集,應該優先使用這個類。例如:
Deque<Integer> stack = new ArrayDeque<Integer>();
5、ArrayList、LinkedList 和 Vector 總結
(1)當集合內的元素需要頻繁插入,刪除操作時應使用 LinkedList;當需要頻繁查詢時,使用 ArrayList(大部分情況是使用 ArrayList);
(2)ArrayList 和 LinkedList 都未實現同步,Vector 是在 ArrayList 的基礎上實現了同步,是線程安全的;
(3)相比而言,LinkedList 佔的內存要比 ArrayList 大(因爲它必須維護下一個和前一個節點的鏈接)。
二、Set
不包含重複元素的集合。
1、HashSet
-
由 HashMap 支持實現,無序,未實現同步,允許 null 值;
-
add, remove, contains and size
:這些方法的時間複雜度爲 O(1),迭代 HashSet 的時間與實際 HashSet 的 size 和內部支持實現的 HashMap 的 capacity 之和成線性關係。所以,如果對迭代性能敏感,就不要把 HashSet 的初始容量設置太高(或者負載因子太低)(實際上是減小 HashMap 的 capacity 值)。
2、LinkedHashSet
-
由 LinkedHashMap 支持實現,有序(保證了元素的插入順序),未實現同步,允許 null 值
-
與 HashSet 相比:需要多維護一個
linked list
,所以總體上性能上會比 HashMap 稍微慢一點,但有一點例外:迭代 LinkedHashSet 的開銷比迭代 HashSet 要小,原因是迭代 LinkedHashSet 只與 size 相關,而迭代 HashSet 還與 capacity 相關。
3、SortedSet
-
元素有序(按照自然排序或 Comparator 排序)
-
內部的元素都必須實現 Comparable 接口,保證集合內部任意兩個元素之間是可比較的
-
subSet(E fromElement, E toElement)
:返回該集合部分元素([from,to))的視圖(view),操作該視圖會映射到原集合上,而且該視圖有限制:當添加一個此範圍([from,to))之外的值會拋 IllegalArgumentException。 -
headSet(E toElement):( < toElement)
:返回一個和 subSet 一樣的視圖([lowestElement, toElement)) -
tailSet(E fromElement):( >= fromElement)
:返回一個和 subSet 一樣的視圖([fromElement, highestElement])
4、TreeSet(SortedSet 的實現)
-
基於 TreeMap 的 NavigableSet 實現;未實現同步。
-
add, remove and contains
:時間複雜度爲 log(n)
三、Queue
爲在處理之前保存元素而設計的集合。
-
提供了額外的插入(offer)、提取(poll)和檢查(peek)操作,當操作失敗時返回 null 或 false,而不是拋出異常。
-
一般來說,隊列都是先進先出(FIFO)的方式,但也有例外:如優先級隊列(PriorityQueue)。
-
隊列實現通常不允許插入空元素,因爲 null 被 poll 方法用作特殊返回值來指示隊列不包含任何元素。
1、PriorityQueue
基於優先級堆(實際上是最小堆)實現,用數組存儲堆;
-
未實現同步(線程安全的優先級隊列:PriorityBlockingQueue),是無界隊列,但有容量(capacity),添加元素時,如果容量不足,會自動增長。
-
iterator() 方法不提供保證以特定的順序遍歷優先級隊列中的元素。
-
offer, poll, remove() and add
的時間複雜度爲 O(log(n)):往堆裏增刪元素 -
remove(Object)
和contains(Object)
爲線性時間:Java 文檔上是寫的這兩個方法的複雜度都是線性時間,即 O(n) ,因爲是用數組存儲堆的,所以 contains 就是遍歷數組查找元素,因此,contains(Object) 的複雜度是 O(n) 是沒有問題的,但是我覺得 remove(Object) 操作的複雜度是 O(n) + O(log(n)),即從數組裏面找到它 O(n),然後從堆裏面刪除它 O(log(n))。(還是我理解錯了,希望有大佬能夠指導一下?) -
peek, element, and size
爲 O(1):因爲是取堆頂元素。
2、Deque
雙向隊列,支持兩端元素的插入與刪除。Deque 也可以用作 LIFO(後進先出)堆棧。這個接口應優先於傳統的 Stack 類使用。
不支持通過索引訪問元素。
雖然 Deque 實現不是嚴格要求禁止插入 null 值,強烈建議任何允許 null 元素的 Deque 實現的用戶不要利用插入空值的能力,因爲null被一些方法用作特殊返回值來指示該雙端隊列是空的。
3、ArrayDeque(Deque 的實現)
-
未實現同步,不允許 null 值。
-
當用作堆棧時,該類可能比 Stack 快,並且在用作隊列時比 LinkedList 快。
參考資料:
(1)Create ArrayList from array
(2)When to use LinkedList over ArrayList?
(3)java中的容器講解
(4)Java ArrayList resize costs
(6)Why should I use Deque over Stack?
(7)Java 文檔