線性表、鏈表、哈希表是常用資料結構,進行 Java 開發時,JDK 已提供一系列相應類實現基本資料結構,均位於 java.util 包中。本文嘗試透過簡單描述,闡述各類別的作用與正確使用方式。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection 是最基本的集合介面,代表一組 Object(即元素)。部分 Collection 允許重複元素,部分不允許;部分可排序,部分不可。Java SDK 不提供直接繼承自 Collection 的類,提供的類皆繼承自其「子介面」如 List 和 Set。
所有實現 Collection 介面的類必須提供兩個標準構造函數:無參構造函數創建空 Collection;有一個 Collection 參數的構造函數創建新 Collection,新 Collection 與傳入 Collection 擁有相同元素,允許用戶複製 Collection。
如何遍歷 Collection 中的每個元素?無論實際類型,皆支援 iterator() 方法,返回迭代器,可逐一訪問元素。典型用法:
Iterator it = collection.iterator();
while(it.hasNext()) {
Object obj = it.next();
}
由 Collection 衍生的兩個介面是 List 和 Set。
boolean add(Object o) 添加物件到集合boolean remove(Object o) 刪除指定物件int size() 返回元素數量boolean contains(Object o) 查找是否有指定物件boolean isEmpty() 判斷是否為空Iterator iterator() 返回迭代器boolean containsAll(Collection c) 查找是否包含集合 c 的元素boolean addAll(Collection c) 將集合 c 所有元素添加給該集合void clear() 刪除所有元素void removeAll(Collection c) 刪除集合 c 中也有的元素void retainAll(Collection c) 刪除集合 c 中不包含的元素List 是有序的 Collection,可精確控制每個元素插入位置。用戶可使用索引(元素在 List 中的位置,類似陣列下標)訪問元素,類似 Java 陣列。與 Set 不同,List 允許重複元素。
除了 iterator(),List 還提供 listIterator() 返回 ListIterator,比標準 Iterator 多出 add() 等方法,允許添加、刪除、設定元素,並能向前或向後遍歷。
實現 List 介面的常用類有 LinkedList、ArrayList、Vector 和 Stack。
void add(int index, Object element) 在指定位置添加物件boolean addAll(int index, Collection c) 將集合 c 元素添加到指定位置Object get(int index) 返回指定位置元素int indexOf(Object o) 返回第一次出現元素 o 的位置Object remove(int index) 刪除指定位置元素Object set(int index, Object element) 用元素 element 取代位置 index 上的元素,返回被取代元素實現 List 介面,允許 null 元素。額外提供在首部或尾部的 get、remove、insert 方法,可作堆疊、佇列或雙向佇列使用。注意 LinkedList 無同步方法,多線程並發訪問需自行實現同步,可用 Collections.synchronizedList(new LinkedList(...)) 構造同步 List。
實現可變大小陣列,允許所有元素包括 null,非同步。size、isEmpty、get、set 運行時間為常數;add 方法攤銷常數時間,添加 n 個元素需 O(n) 時間,其他方法線性時間。每個實例有容量,隨添加元素自動增加,增長演算法未定義。需插入大量元素時,可預先呼叫 ensureCapacity 提高效率。同 LinkedList,非同步。
與 ArrayList 相似,但 Vector 是同步的。由 Vector 創建的 Iterator 雖與 ArrayList 同介面,但因 Vector 同步,若一個 Iterator 被創建並使用時,另一線程改變 Vector 狀態(如增刪元素),調用 Iterator 方法將拋出 ConcurrentModificationException,必須捕獲該異常。
繼承自 Vector,實現後進先出堆疊。提供 5 個額外方法使 Vector 可作堆疊使用:基本 push 和 pop,peek 取得堆疊頂元素,empty 測試是否為空,search 檢測元素在堆疊中的位置。新創建的 Stack 為空堆疊。
Set 是不包含重複元素的 Collection,任意兩元素 e1、e2 滿足 e1.equals(e2)=false,最多一個 null 元素。構造函數有約束:傳入的 Collection 參數不能包含重複元素。須小心操作可變物件:若 Set 中可變元素改變狀態導致 Object.equals(Object)=true 將引發問題。
Map 未繼承 Collection 介面,提供 key 到 value 的映射。一個 Map 不能包含相同 key,每個 key 只映射一個 value。Map 介面提供 3 種集合視圖:key 集合、value 集合、key-value 映射集合。
boolean equals(Object o) 比較物件boolean remove(Object o) 刪除物件put(Object key, Object value) 添加 key 和 value繼承 Map 介面,實現 key-value 映射哈希表。任何非空物件可作 key 或 value。put(key, value) 添加,get(key) 取出,兩基本操作時間開銷為常數。透過 initial capacity 和 load factor 調整性能,預設 load factor 0.75 較好平衡時間與空間。增大 load factor 可節省空間但查找時間增大,影響 get 和 put。
簡單示例:
Hashtable numbers = new Hashtable();
numbers.put("one", new Integer(1));
numbers.put("two", new Integer(2));
numbers.put("three", new Integer(3));
Integer n = (Integer)numbers.get("two");
System.out.println("two = " + n);
作為 key 的物件將通過計算散列函數確定 value 位置,故任何作 key 的物件必須實現 hashCode 和 equals 方法。若兩物件相同(equals 為 true),其 hashCode 必相同;但不同物件 hashCode 不一定不同,若不同物件 hashCode 相同稱為衝突,會增加操作時間,故盡量定義好的 hashCode() 能加快操作。若相同物件有不同 hashCode,哈希表操作會出現意外結果(期待的 get 返回 null),避免此問題只需牢記:同時覆寫 equals 和 hashCode,而非只寫其中一個。Hashtable 是同步的。
與 Hashtable 類似,不同處在於 HashMap 非同步,且允許 null(null value 和 null key)。但將 HashMap 視為 Collection 時(values() 可返回 Collection),其迭代器操作時間開銷與容量成比例。因此若迭代操作性能重要,勿將初始化容量設過高或 load factor 過低。
改進的 HashMap,對 key 實行「弱引用」,若 key 不再被外部引用,該 key 可被 GC 回收。
LinkedList;需快速隨機訪問元素,用 ArrayList。equals 和 hashCode。List 而非 ArrayList),日後需將 ArrayList 換成 LinkedList 時,客戶端代碼無需改變,這就是針對抽象編程。