16、Collection介面及其子介面Set和List(常用類LinkedList,ArrayList,Vector和Stack)
Collection是最基本的集合介面,一個Collection代表一組Object,即Collection的元素(Elements)。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子介面”如List和Set。
所有實現Collection介面的類都必須提供兩個標準的建構函式:無引數的建構函式用於建立一個空的Collection,有一個Collection引數的建構函式用於建立一個新的Collection,這個新的Collection與傳入的Collection有相同的元素。後一個建構函式允許使用者複製一個Collection。
遍歷Collection中元素:不論Collection的實際型別如何,它都支援一個iterator()的方法,該方法返回一個迭代子,使用該迭代子即可逐一訪問Collection中每一個元素。典型的用法如下:
Iterator it = collection.iterator(); // 獲得一個迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一個元素
}
16.1、Set介面
Set是一種不包含重複元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。Set的建構函式有一個約束條件,傳入的Collection引數不能包含重複的元素。
請注意:必須小心操作可變物件(Mutable Object)。如果一個Set中的可變元素改變了自身狀態,將導致Object.equals(Object)=true將導致一些問題。
16.2、List介面
List是有序的Collection,使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似於陣列下標)來訪問List中的元素,類似於Java的陣列。和上面的Set不同,List允許有相同的元素。
除了具有Collection介面必備的iterator()方法外,List還提供一個listIterator()方法,返回一個ListIterator介面,和標準的Iterator介面相比,ListIterator多了一些add()之類的方法,允許新增,刪除,設定元素,還能向前或向後遍歷。
16.2.1、List介面常用類
AbstractList 是一個抽象類,它繼承於AbstractCollection。AbstractList實現List介面中除size()、get(int location)之外的函式。
AbstractSequentialList 是一個抽象類,它繼承於AbstractList。AbstractSequentialList 實現了“連結串列中,根據index索引值操作連結串列的全部函式”。
ArrayList 是一個數組佇列,相當於動態陣列。它由陣列實現,隨機訪問效率高,隨機插入、隨機刪除效率低。
LinkedList 是一個雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。LinkedList隨機訪問效率低,但隨機插入、隨機刪除效率低。
Vector 是向量佇列,和ArrayList一樣,它也是一個動態陣列,由陣列實現。但是ArrayList是非執行緒安全的,而Vector是執行緒安全的。
Stack 是棧,它繼承於Vector。它的特性是:先進後出(FILO, First In Last Out)。
16.2.1.1、LinkedList類
LinkedList實現了List介面,允許null元素。此外LinkedList提供額外的get,remove,insert方法在LinkedList的首部或尾部。這些操作使LinkedList可被用作堆疊(stack),佇列(queue)或雙向佇列(deque)。
注意LinkedList沒有同步方法。如果多個執行緒同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在建立List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
16.2.1.2、ArrayList類
ArrayList實現了可變大小的陣列。它允許所有元素,包括null。ArrayList沒有同步。size,isEmpty,get,set方法執行時間為常數。但是add方法開銷為分攤的常數,新增n個元素需要O(n)的時間。其他的方法執行時間為線性。
每個ArrayList例項都有一個容量(Capacity),即用於儲存元素的陣列大小。這個容量可隨著不斷新增新元素而自動增加,但是增長演算法並沒有定義。當需要插入大量元素時,在插入前可以呼叫ensureCapacity方法來增加ArrayList的容量以提高插入效率。和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。
16.2.1.3、Vector類
Vector非常類似ArrayList,但是Vector是同步的。由Vector建立的Iterator,雖然和ArrayList建立的Iterator是同一介面,但是,因為Vector是同步的,當一個Iterator被建立而且正在被使用,另一個執行緒改變了Vector的狀態(例如,新增或刪除了一些元素),這時呼叫Iterator的方法時將丟擲ConcurrentModificationException,因此必須捕獲該異常。
16.2.1.4、Stack 類
Stack繼承自Vector,實現一個後進先出的堆疊。Stack提供5個額外的方法使得Vector得以被當作堆疊使用。基本的push和pop方法,還有peek方法得到棧頂的元素,empty方法測試堆疊是否為空,search方法檢測一個元素在堆疊中的位置。Stack剛建立後是空棧。
16.2.2、List介面使用場景
如果涉及到“棧”、“佇列”、“連結串列”等操作,應該考慮用List,具體的選擇哪個List,根據下面的標準來取捨。
(01) 對於需要快速插入,刪除元素,應該使用LinkedList。
通過add(int index, E element)向LinkedList插入元素時。先是在雙向連結串列中找到要插入節點的位置index;找到之後,再插入一個新節點。雙向連結串列查詢index位置的節點時,有一個加速動作:若index < 雙向連結串列長度的1/2,則從前向後查詢; 否則,從後向前查詢。
ArrayList的add(int index, E element)函式,會引起index之後所有元素的改變,會將index之後所有元素都向後移動一位,從而使ArrayList中插入元素很慢。“刪除元素”與“插入元素”的原理類似。
(02) 對於需要快速隨機訪問元素,應該使用ArrayList。
通過get(int index)獲取LinkedList第index個元素時。先是在雙向連結串列中找到index位置的元素,找到之後再返回。雙向連結串列查詢index位置的節點時,有一個加速動作:若index < 雙向連結串列長度的1/2,則從前向後查詢; 否則,從後向前查詢。
通過get(int index)獲取ArrayList第index個元素時。直接返回陣列中index位置的元素,而不需要像LinkedList一樣進行查詢。
(03) 對於“單執行緒環境” 或者 “多執行緒環境,但List僅僅只會被單個執行緒操作”,此時應該使用非同步的類(如ArrayList)。
(04) 對於“多執行緒環境,且List可能同時被多個執行緒操作”,此時,應該使用同步的類(如Vector)。
16.2.2.1、Vector和ArrayList比較
相同之處:
(1) 它們都是List,都繼承於AbstractList,並且實現List介面。
(2) 它們都實現了RandomAccess和Cloneable介面。實現RandomAccess介面,意味著它們都支援快速隨機訪問;實現Cloneable介面,意味著它們能克隆自己。
(3) 它們都是通過陣列實現的,本質上都是動態陣列。ArrayList.java中定義陣列elementData用於儲存元素;Vector.java中也定義了陣列elementData用於儲存元素
(4) 它們的預設陣列容量是10。若建立ArrayList或Vector時,沒指定容量大小;則使用預設容量大小10。
(5) 它們都支援Iterator和listIterator遍歷。它們都繼承於AbstractList,而AbstractList中分別實現了 “iterator()介面返回Iterator迭代器” 和 “listIterator()返回ListIterator迭代器”。
不同之處:
(1) 執行緒安全性不一樣。ArrayList是非執行緒安全;而Vector是執行緒安全的,它的函式都是synchronized的,即都是支援同步的。ArrayList適用於單執行緒,Vector適用於多執行緒。
(2) 對序列化支援不同。ArrayList支援序列化,而Vector不支援;即ArrayList有實現java.io.Serializable介面,而Vector沒有實現該介面。
(3) 建構函式個數不同。ArrayList有3個建構函式,而Vector有4個建構函式。Vector除了包括和ArrayList類似的3個建構函式之外,另外的一個建構函式可以指定容量增加係數。
ArrayList的建構函式如下:
Vector的建構函式如下:// 預設建構函式 ArrayList() // capacity是ArrayList的預設容量大小。當由於增加資料導致容量不足時,容量會新增上一次容量大小的一半。 ArrayList(int capacity) // 建立一個包含collection的ArrayList ArrayList(Collection<? extends E> collection)
// 預設建構函式 Vector() // capacity是Vector的預設容量大小。當由於增加資料導致容量增加時,每次容量會增加一倍。 Vector(int capacity) // 建立一個包含collection的Vector Vector(Collection<? extends E> collection) // capacity是Vector的預設容量大小,capacityIncrement是每次Vector容量增加時的增量值。 Vector(int capacity, int capacityIncrement)
(4) 容量增加方式不同。逐個新增元素時,若ArrayList容量不足時,“新的容量”=“(原始容量x3)/2 + 1”;而Vector的容量增長與“增長係數有關”,若指定了“增長係數”,且“增長係數有效(即,大於0)”;那麼,每次容量不足時,“新的容量”=“原始容量+增長係數”。若增長係數無效(即,小於/等於0),則“新的容量”=“原始容量 x 2”。
(5) 對Enumeration的支援不同。Vector支援通過Enumeration去遍歷,而List不支援