大佬們好!我是LKJ_Coding,一枚初級馬牛,正在努力在代碼的叢林中找尋自己的方向。如果你也曾在調試中迷失,或是在文檔中翻滾,那我們一定有許多共同話題可以聊!今天,我帶着滿滿的代碼“乾貨”來和大家分享,學不學無所謂,反正我先吐槽了!
前言
你説你學 Java,也用過 ArrayList、HashMap,但我問你:HashMap 是數組 + 鏈表 + 紅黑樹的結構,你能畫出來嗎?
你用 ConcurrentHashMap 併發場景穩如老狗,那你知道它是怎麼實現 Segment 到 CAS 優化的嗎?
今天我們不談“我會用”,我們談“我徹底懂”。集合框架是 Java 的基礎設施級能力,是寫代碼的底氣、也是調優的核心戰場。
你以為是簡單裝數據?其實是底層數據結構、併發模型、內存佈局的精妙結合。
📚 一、List、Set、Map:分類很簡單,實現細節夠嗆
Java 的集合,按照用途分類,基本三大派系:
| 類型 | 接口 | 常見實現類 |
|---|---|---|
| List | 有序、可重複 | ArrayList, LinkedList, Vector |
| Set | 無序、不可重複 | HashSet, LinkedHashSet, TreeSet |
| Map | 鍵值對結構 | HashMap, TreeMap, LinkedHashMap, Hashtable, ConcurrentHashMap |
📦 List —— 順序為王,重複無罪
- ArrayList:底層是動態數組,查詢快,增刪慢(涉及元素搬遷)。
- LinkedList:雙向鏈表結構,插入刪除快,隨機訪問慢。
- Vector:線程安全的 ArrayList(用 synchronized 包裹),老古董。
🌰 ArrayList 結構一瞥
Object[] elementData;
int size;
動態擴容邏輯(JDK 1.8):
int newCapacity = oldCapacity + (oldCapacity >> 1); // 擴容為原來的1.5倍
🌀 Set —— 唯一性第一
- HashSet:基於 HashMap 實現,底層用的是 HashMap 的 key。
- LinkedHashSet:有序的 HashSet,維護插入順序。
- TreeSet:基於 TreeMap 實現,自動排序(紅黑樹)。
🌰 HashSet 背後是個 HashMap
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
🧩 Map —— 讓鍵值對説話
- HashMap:最常用,非線程安全,底層數組 + 鏈表/紅黑樹
- TreeMap:排序的 Map,底層是紅黑樹
- LinkedHashMap:帶順序的 HashMap,可做 LRU 緩存
- ConcurrentHashMap:線程安全的分段鎖 + CAS 實現
🔬 二、HashMap 底層原理(JDK7 vs JDK8):你以為 put 進去就完事了?
面試官最愛問的經典三連:
- HashMap 的底層結構是啥?
- 為什麼會產生 hash 衝突?
- JDK8 之後有啥優化?
📌 JDK7 的 HashMap 結構
數組 + 鏈表
- 通過
hash(key)得到 hash 值 index = hash & (table.length - 1)定位桶- 衝突了就鏈表法解決衝突
📌 JDK8 的 HashMap 結構
數組 + 鏈表 + 紅黑樹(三合一)
- 當鏈表長度超過 8 且數組長度 ≥ 64,則轉為紅黑樹
- 紅黑樹結構提高查詢效率,避免鏈表 O(n)
🧠 put() 操作核心步驟
- 判斷數組是否初始化(懶加載)
- 計算 hash 並定位 bucket
- 如果 bucket 為空,直接插入
- 如果有衝突,遍歷鏈表或紅黑樹
- 判斷是否需要轉樹或擴容
- size++,檢查 threshold 是否達標
🌰 put() 核心代碼簡化版(JDK8)
if (table == null || table.length == 0) resize();
int hash = hash(key);
int i = (n - 1) & hash;
if (table[i] == null) {
table[i] = new Node<>(...);
} else {
// 鏈表遍歷 或 紅黑樹插入
}
🧱 三、ConcurrentHashMap 併發設計:線程安全不是靠 synchronized
你可能覺得,加個 synchronized 就萬事大吉?別逗了,JDK 1.8 的 ConcurrentHashMap 可是武裝到了牙齒。
📌 JDK7 的 Segment 分段鎖
- 將整個 Map 拆成多個 Segment,每段加鎖
- Segment 是一個 mini-HashMap
- 併發度 = Segment 數量(默認 16)
優點:分段加鎖提升併發 缺點:結構複雜,鎖粒度仍較大
📌 JDK8 的徹底重構
- 放棄 Segment
- 用 CAS + synchronized(在桶級別)
- 數據結構與 HashMap 保持一致:數組 + 鏈表 + 紅黑樹
🧠 核心優化點
- 利用 CAS 保證節點插入原子性
- 鏈表插入使用 synchronized 鎖桶節點
- 紅黑樹寫操作仍加鎖,讀儘量無鎖
🌰 實例代碼
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("A", "Apple");
map.get("A");
你覺得它簡單,其實內部已經繞過了多個線程安全陷阱。
💥 四、fail-fast vs fail-safe:你知道為啥遍歷時不能改集合嗎?
你寫了這麼久集合,是否被下面這個錯誤嚇過:
Exception in thread "main" java.util.ConcurrentModificationException
這就是 Java 的fail-fast 機制在作怪。
🧨 fail-fast
- 出現在普通集合:
ArrayList,HashMap - 結構發生變化時,遍歷器立刻拋出異常
- 基於
modCount檢查結構修改次數
🌰 例子:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
if ("A".equals(s)) {
list.remove(s); // 報錯!
}
}
✅ fail-safe
- 出現在併發包:
ConcurrentHashMap,CopyOnWriteArrayList - 修改不影響當前遍歷(通過複製、快照)
- 性能略低,但安全
🌰 例子:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
if ("A".equals(s)) {
list.remove(s); // ✅ 不報錯
}
}
🧾 總結:集合不只是存東西,它決定了你程序的底氣
Java 集合不是工具人,它是存儲結構 + 算法思想 + 併發控制的綜合體現。 寫業務代碼可以粗糙,但操作集合時你得像做外科手術一樣精準。
你不懂 HashMap,就搞不清緩存; 你不懂 ConcurrentHashMap,就別談併發安全; 你不理解 fail-fast,就等着線上崩潰給你寫教訓。
最後送你一段總結性“靈魂質問”:
“你真懂 Java 集合了嗎?還是隻是會用 ArrayList 來湊合?”
好啦,廢話不多説,今天的分享就到這裏!如果你覺得我這“初級馬牛”還挺有趣,那就請給我點個贊、留個言、再三連擊三連哦!讓我們一起“成精”吧!下次見,不見不散!