Java Connection集合分析之List

Java Connection集合家庭分析

Java集合大致可以分爲Set、List、Queue和Map四種體系,其中Set代表無序、不可重複的集合;List代表有序、重複的集合;而Map則代表具有映射關係的集合,Java 5 又增加了Queue體系集合,代表一種隊列集合實現。

Java集合類之間的繼承關係

Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。

Collection家族:

 

List集合

List集合是有序、可重複的集合

本篇文章將集中介紹List集合的兩個重要子類ArrayList(並且同時對比Vector)和LinkedList。

 

1.List集合中,判斷元素是否相等需要集合元素類重寫equals()方法。

 

2.List集合中,我們可以使用ListIterator來提供更加厲害的表裏操作,ListIterator相比Iterator,可以對集合元素在遍歷期間進行修改、插入、刪除操作,並且還可以向前遍歷。這些在Iterator中是無法做到的,其原因是因爲List的底層採用數組結構實現,用數組意味着就有標記來記錄各個元素的位置,因此針對List集合可以通過ListIterator在遍歷期間進行集合元素的修改操作。

 

3.ArrayList與Vector的相同點在於底層都爲數組實現,並且的默認初始長度爲10。不同地方在於,進行擴容時,ArrayList每次擴容增加當前容量的50%,Vector則增加一倍。ArrayList爲線程不安全,Vector爲線程安全。ArrayList遍歷方式有Iterator、ListIterator、for循環、增強for循環,Vector遍歷方式有Iterator、ListIterator、for循環、增強for循環、枚舉Enumeration。

我們分別看下Vector與ArrayList的初始化構造函數:

 

 

再看下Vector與ArrayList的add方法,觀察其擴容策略:

Vector

 

Vector進行擴容操作的邏輯爲

如果此向量的當前容量小於minCapacity,則通過將其內部數組替換爲一個較大的數組倆增加其容量。

新數據數組的大小姜維原來的大小 + capacityIncrement,

除非 capacityIncrement 的值小於等於零,在後一種情況下,新的容量將爲原來容量的兩倍,不過,如果此大小仍然小於 minCapacity,則新容量將爲 minCapacity。

capacityIncrement爲容量增量,就是每次Vector進行擴容的長度,在Vector進行初始化的時候可以指定capacityIncrement,否則默認爲0。也就是擴容一倍。

 

ArrayList

 

ArrayList的擴容邏輯除了標紅處之外,其餘和Vector相同。

 

4.如果開始就知道ArrayList或Vector集合需要保存多少個元素,則可以在創建它們時就指定initalCapacity初始長度的大小,這樣可以提高性能。

此外,ArrayList還提供了兩個額外的方法來調整其容量大小:

void ensureCapacity(int minCapacity) //如有必要,增加此 ArrayList 實例的容量,以確保它至少能夠容納最小容量參數所指定的元素數。

void trimToSize() //將此 ArrayList 實例的容量調整爲列表的當前大小。

 

5.Stack是Vector的子類,用於模擬“棧”這種數據結構,“棧”通常是指“後進先出”(LIFO)的容器。最後“push”進棧的元素,將被最先“pop”出棧。Stack與Vector一樣,是線程安全的,性能較差,儘量少用Stack類。如果要實現棧”這種數據結構,可以考慮使用LinkedList。

 

6.LinkedList類是List接口的實現類——這意味着它是一個List集合,可以根據索引來隨機訪問集合中的元素。除此之外,LinkedList還實現了Deque接口(繼承了Queue接口的雙端隊列),可以被當作成雙端隊列來使用,因此既可以被當成“棧"來使用,也可以當成隊列來使用。

LinkedList的實現機制與ArrayList完全不同。ArrayList內部是以數組的形式來保存集合中的元素的,因此隨機訪問集合元素時有較好的性能;而LinkedList內部以鏈表的形式來保存集合中的元素,因此隨機訪問集合元素時性能較差,但在插入、刪除元素時性能比較出色。

 

7.LinkedList調用默認構造函數,創建一個鏈表。由於維護了一個表頭,表尾的Node對象的變量。可以進行後續的添加元素到鏈表中的操作,以及其他刪除,插入等操作。也因此實現了雙向隊列的功能,即可向表頭加入元素,也可以向表尾加入元素。

 

下面來了解Node類的具體情況

 

由此可以具體瞭解鏈表是如何串聯起來並且每個節點包含了傳入集合的元素。

下面以增加操作,具體瞭解LinkedList的工作原理。

調用linkLast(e);方法,默認向表尾節點加入新的元素

 

更新表尾節點,建立連接。其他操作類似,維護了整個鏈表。

下面具體來看,如何將“雙向鏈表和索引值聯繫起來的”?並且爲什麼說LinkedList通過下標訪問效率低?

 

調用了node(index)方法返回了一個Node對象,其中node(index)方法具體如下

首先會比較“index”和“雙向鏈表長度的1/2”;若前者小,則從鏈表頭開始往後查找,直到index位置;否則,從鏈表末尾開始先前查找,直到index位置。這就是“雙線鏈表和索引值聯繫起來”的方法。

到此我們便會明白,LinkedList在插入、刪除元素時性能比較出色,隨機訪問集合元素時性能較差。

 

8.LinkedList遍歷方式

 

LinkedList支持多種遍歷方式。

.通過迭代器遍歷LinkedList

.通過快速隨機訪問遍歷LinkedList(低效)

.通過for循環遍歷LinkedList

.通過pollFirst()遍歷LinkedList

.通過pollLast()遍歷LinkedList

.通過removeFirst()遍歷LinkedList

.通過removeLast()遍歷LinkedList

實現都比較簡單,就不貼代碼了。

其中採用逐個遍歷的方式,效率比較高。採用隨機訪問的方式去遍歷LinkedList的方式效率最低。

LinkedList也是非線程安全的。

 

9.ArrayList與LinkedList性能對比

ArrayList 是一個數組隊列,相當於動態數組。它由數組實現,隨機訪問效率高,隨機插入、隨機刪除效率低。ArrayList應使用隨機訪問(即,通過索引序號訪問)遍歷集合元素。

LinkedList 是一個雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作。LinkedList隨機訪問效率低,但隨機插入、隨機刪除效率高。LinkedList應使用採用逐個遍歷的方式遍歷集合元素。

如果涉及到“動態數組”、“棧”、“隊列”、“鏈表”等結構,應該考慮用List,具體的選擇哪個List,根據下面的標準來取捨。

(01) 對於需要快速插入,刪除元素,應該使用LinkedList。

(02) 對於需要快速隨機訪問元素,應該使用ArrayList。

(03) 針對迭代的效率問題

LinkedList:高級for循環與Iterator迭代(效率相似)>下標訪問迭代。

ArrayList:下標訪問迭代>高級for循環與Iterator迭代(效率相似)。

測試數據如下: 基於JDK1.8

LinkedList,ArrayList分別通過上面三種方式,遍歷10w次String對象的效率

 

遍歷100w次

 

遍歷300w次

 

Iterator遍歷會被JVM編譯爲

 

而高級for循環會被編譯爲

 

(04) 數組和鏈表的遍歷效率問題(後續討論。。。)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章