Java 集合框架是數據存儲與操作的利器。本文以記事本為例,演示 ArrayList 有序可重複、HashSet 無序去重、HashMap 鍵值映射三大核心容器的典型用法、內部機制與遍歷技巧,並解析 toString() 打印原理。
public class Notebook {
private ArrayList<String> notes = new ArrayList<>(); // 泛型指定 String
public void add(String s) { notes.add(s); }
public int getSize() { return notes.size(); }
public void removeNote(int index) { notes.remove(index); }
public String getNote(int index) { return notes.get(index); }
public String[] list() {
return notes.toArray(new String[0]); // JDK 11+ 推薦 new String[0]
}
public static void main(String[] args) {
Notebook nb = new Notebook();
nb.add("first"); nb.add("second");
System.out.println(nb.getSize()); // 2
for (String s : nb.list()) System.out.println(s);
}
}
| 操作 | 時間複雜度 | 說明 |
|---|---|---|
| add(E) | O(1) 攤還 | 尾部添加,擴容時 O(n) |
| add(index, E) | O(n) | 需搬移後續元素 |
| get(index) | O(1) | 數組隨機訪問 |
| remove(index) | O(n) | 搬移元素填補空位 |
| contains(E) | O(n) | 線性掃描 |
newCapacity = old + (old >> 1))。new ArrayList<>(capacity) 避免多次擴容拷貝。Set<String> set = new HashSet<>();
set.add("A"); set.add("B"); set.add("A"); // 重複不添加
System.out.println(set); // [A, B] 順序不固定
HashSet 內部持有一個 HashMap<E, Object>,所有鍵映射到同一個 PRESENT 虛擬值。hashCode() 與 equals() 契約:自定義對象作為元素必須重寫這兩個方法。Map<Integer, String> coins = new HashMap<>();
coins.put(1, "penny");
coins.put(5, "nickel");
coins.put(10, "dime");
coins.put(25, "quarter");
coins.put(1, "one cent"); // 鍵 1 覆蓋,值更新為 "one cent"
System.out.println(coins.get(1)); // one cent
System.out.println(coins.size()); // 4
// 1. entrySet - 最通用,同時獲取鍵值 (推薦)
for (Map.Entry<Integer, String> entry : coins.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 2. keySet + get - 需額外查找值,稍慢
for (Integer key : coins.keySet()) {
System.out.println(key + " -> " + coins.get(key));
}
// 3. forEach (JDK 8+) - 函數式風格,代碼簡潔
coins.forEach((k, v) -> System.out.println(k + " -> " + v));
hashCode() 決定桶位置,equals() 解決哈希衝突。集合框架所有實現類 (ArrayList, HashSet, HashMap 等) 繼承自 AbstractCollection,覆寫了 toString():
// AbstractCollection.toString() 簡化版
public String toString() {
Iterator<E> it = iterator();
if (!it.hasNext()) return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : String.valueOf(e));
if (!it.hasNext()) return sb.append(']').toString();
sb.append(',').append(' ');
}
}
結論:集合打印本質是迭代調用每個元素的 toString()。自定義類若想友好打印,**必須重寫 toString()**。
| 需求 | 推薦容器 | 關鍵點 |
|---|---|---|
| 頻繁隨機訪問、尾部增刪、允許重複 | ArrayList | 預設容量、避免中間插入 |
| 頻繁頭部/中間增刪、順序訪問 | LinkedList | 雙向鏈表、實現 Deque |
| 去重、無序、快速判斷存在性 | HashSet | 重寫 hashCode/equals |
| 有序去重 (插入序/自然序) | LinkedHashSet / TreeSet | TreeSet 需實現 Comparable |
| 鍵值映射、鍵唯一、高頻查找 | HashMap | 鍵不可變、預估容量 |
| 有序映射 (插入序/鍵排序) | LinkedHashMap / TreeMap | TreeMap 鍵需 Comparable |
| 多線程高併發 | ConcurrentHashMap / CopyOnWriteArrayList | 分段鎖 / 寫時復制 |
new ArrayList<>(1000000) 顯式指定大容量可能 OOM,建議分批處理或用流式 API。list.remove() → ConcurrentModificationException,請用 Iterator.remove() 或 removeIf()。hashCode/equals → 無法正確去重/查找。HashMap 存儲大量長期存活對象導致內存洩漏,考慮 WeakHashMap 或定期清理。List.of(), Set.of(), Map.of() 創建不可變集合,線程安全且內存緊湊。掌握三大容器的底層數據結構 (動態數組、哈希表、紅黑樹) 與核心契約 (hashCode/equals),就能在面對海量業務場景時,憑直覺選出最優容器,寫出高性能、低 Bug 的代碼。