大佬們好!我是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 進去就完事了?

  面試官最愛問的經典三連:

  1. HashMap 的底層結構是啥?
  2. 為什麼會產生 hash 衝突?
  3. JDK8 之後有啥優化?

📌 JDK7 的 HashMap 結構

數組 + 鏈表
  • 通過 hash(key) 得到 hash 值
  • index = hash & (table.length - 1) 定位桶
  • 衝突了就鏈表法解決衝突

📌 JDK8 的 HashMap 結構

數組 + 鏈表 + 紅黑樹(三合一)
  • 當鏈表長度超過 8 且數組長度 ≥ 64,則轉為紅黑樹
  • 紅黑樹結構提高查詢效率,避免鏈表 O(n)

🧠 put() 操作核心步驟

  1. 判斷數組是否初始化(懶加載)
  2. 計算 hash 並定位 bucket
  3. 如果 bucket 為空,直接插入
  4. 如果有衝突,遍歷鏈表或紅黑樹
  5. 判斷是否需要轉樹或擴容
  6. 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 來湊合?”

好啦,廢話不多説,今天的分享就到這裏!如果你覺得我這“初級馬牛”還挺有趣,那就請給我點個贊、留個言、再三連擊三連哦!讓我們一起“成精”吧!下次見,不見不散!