最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JDK8 HashMap紅黑樹退化為鏈表的機制方式

 更新時間:2025年05月13日 09:58:48   作者:找不到、了  
這篇文章主要介紹了JDK8 HashMap紅黑樹退化為鏈表的機制方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

1、數(shù)據(jù)結(jié)構(gòu)

jdk8及之后,由hashmap由數(shù)組+鏈表(紅黑樹組成)。

如下圖所示:

桶數(shù)組是用來存儲數(shù)據(jù)元素,鏈表是用來解決沖突,紅黑樹是為了提高查詢的效率。

數(shù)據(jù)元素通過映射關(guān)系,也就是散列函數(shù),映射到桶數(shù)組對應索引的位置。

如下圖所示:

如果發(fā)生沖突,從沖突的位置拉一個鏈表,插入沖突的元素。

如果鏈表長度>8&數(shù)組大小>=64,鏈表轉(zhuǎn)為紅黑樹。

如果紅黑樹節(jié)點個數(shù)<6 ,轉(zhuǎn)為鏈表。

2、Fail-Fast機制

Fail-Fast(快速失?。┦荍ava集合框架中一種重要的并發(fā)修改檢測機制,在HashMap中主要用于防止在迭代過程中集合被意外修改而導致數(shù)據(jù)不一致的問題。

2.1、核心作用

Fail-Fast機制就像集合的"安全警報系統(tǒng)":

  • 實時監(jiān)控:檢測迭代期間的意外修改
  • 快速響應:立即拋出ConcurrentModificationException
  • 預防損害:避免產(chǎn)生不可預知的錯誤結(jié)果

2.2、實現(xiàn)原理

1. 關(guān)鍵變量

// HashMap中的修改計數(shù)器
transient int modCount;

// 迭代器中保存的計數(shù)器快照
int expectedModCount;

2. 工作流程

2.3、觸發(fā)場景

1. 迭代時修改集合

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
    String key = it.next();
    map.put("C", 3);  // 這里會觸發(fā)fail-fast
}

2. 多線程并發(fā)修改

Map<Integer, String> map = new HashMap<>();
map.put(1, "One");

new Thread(() -> {
    map.put(2, "Two");  // 可能觸發(fā)主線程迭代時fail-fast
}).start();

for (Integer key : map.keySet()) {  // 可能拋出異常
    System.out.println(key);
}

2.4、實現(xiàn)細節(jié)

1. 修改計數(shù)更新點

// HashMap中的修改操作都會增加modCount
public V put(K key, V value) {
    // ...
    ++modCount;
    // ...
}

public V remove(Object key) {
    // ...
    ++modCount;
    // ...
}

public void clear() {
    // ...
    ++modCount;
    // ...
}

2. 迭代器檢查點

final class KeyIterator extends HashIterator 
    implements Iterator<K> {
    public final K next() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        // ...
    }
}

2.5、對比

2.6、注意事項

1.正確刪除元素

// 錯誤方式(觸發(fā)fail-fast)
for (String key : map.keySet()) {
    if (key.equals("remove")) {
        map.remove(key);  // 直接修改原集合
    }
}

// 正確方式(使用迭代器的remove)
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
    if (it.next().equals("remove")) {
        it.remove();  // 不會增加modCount
    }
}

2.多線程解決方案

  • 使用ConcurrentHashMap替代
  • 或者使用顯式同步:
synchronized(map) {
    for (String key : map.keySet()) {
        // 操作代碼
    }
}

3.性能監(jiān)控

// 檢測異常頻率
try {
    for (Entry<K,V> e : map.entrySet()) {
        // ...
    }
} catch (ConcurrentModificationException ex) {
    metrics.record("fail-fast.triggered");
}

Fail-Fast機制雖然會給開發(fā)者帶來一些"麻煩",但它有效地預防了更危險的隱性數(shù)據(jù)一致性問題,是Java集合框架健壯性的重要保障。

理解這一機制可以幫助開發(fā)者寫出更安全的集合操作代碼。

3、核心結(jié)論

在JDK8+的HashMap中:

  • 確實存在紅黑樹退化為鏈表的機制(當節(jié)點數(shù)≤6時)
  • 這不是紅黑樹自身的特性,而是HashMap的主動優(yōu)化
  • 轉(zhuǎn)換是安全的,因為這是在擴容(resize)或刪除(remove)時觸發(fā)的

關(guān)于樹化與退化閾值如下圖所示:

4、轉(zhuǎn)化安全機制

HashMap在JDK8引入的紅黑樹轉(zhuǎn)換機制包含嚴格的安全保障措施,確保在鏈表與紅黑樹相互轉(zhuǎn)換時不會破壞數(shù)據(jù)一致性和線程安全。

4.1. 觸發(fā)場景

1. 樹化(鏈表 → 紅黑樹)條件

使用treeifybin()方法。

// HashMap.treeifyBin() 片段
if (binCount >= TREEIFY_THRESHOLD - 1) { // TREEIFY_THRESHOLD=8
    if (tab.length < MIN_TREEIFY_CAPACITY) // MIN_TREEIFY_CAPACITY=64
        resize();
    else
        treeifyBin(tab, hash);
}

雙重校驗保障

單鏈表長度≥8

哈希表容量≥64

  • 避免小表頻繁樹化
  • 確保有足夠分散的桶空間

2. 退化(紅黑樹 → 鏈表)條件

// HashMap.resize() 片段
if (lc <= UNTREEIFY_THRESHOLD)  // UNTREEIFY_THRESHOLD=6
    tab[index] = loHead.untreeify(map);

安全邊界

  • 樹節(jié)點≤6時才退化(比樹化閾值低2,避免頻繁轉(zhuǎn)換)

4.2. 轉(zhuǎn)換過程

如下圖所示:

1. 鏈表→紅黑樹轉(zhuǎn)換流程

關(guān)鍵保障

  • 持有桶頭節(jié)點鎖再進行轉(zhuǎn)換
  • 新建TreeNode時保留原鏈表順序(通過next指針)
  • 平衡操作不改變元素哈希位置

2. 紅黑樹→鏈表轉(zhuǎn)換流程

如下圖所示:

代碼示例:

// TreeNode.untreeify() 實現(xiàn)
final Node<K,V> untreeify(HashMap<K,V> map) {
    Node<K,V> hd = null, tl = null;
    for (TreeNode<K,V> q = this; q != null; q = q.next) {
        Node<K,V> p = map.replacementNode(q, null); // 新建普通節(jié)點
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    return hd;
}

安全保障

  • 按原有鏈表順序(通過TreeNode保留的next指針)重建
  • 新建普通節(jié)點而非修改原節(jié)點,避免并發(fā)訪問問題
  • 轉(zhuǎn)換完成后原TreeNode可被GC回收

4.3. 并發(fā)安全機制

1、轉(zhuǎn)換期間不影響迭代器一致性

abstract class HashIterator {
    Node<K,V> next;        // 下一個返回的節(jié)點
    Node<K,V> current;     // 當前節(jié)點
    int expectedModCount;  // 修改計數(shù)器快照
    
    final Node<K,V> nextNode() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        // ...
    }
}

失效保護

  • 迭代期間檢測modCount變化
  • 快速失敗(fail-fast)機制

2、始終維持元素的原始存儲順序

1. 雙向鏈表維護

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 紅黑樹父節(jié)點
    TreeNode<K,V> left;    // 左子樹
    TreeNode<K,V> right;   // 右子樹
    TreeNode<K,V> prev;    // 鏈表前驅(qū)節(jié)點(刪除時需要)
    boolean red;
    // 仍然保留next指針(繼承自Entry)
}

雙重結(jié)構(gòu)

紅黑樹結(jié)構(gòu):parent/left/right

鏈表結(jié)構(gòu):next/prev

  • 保證在退化時可以快速重建鏈表
  • 支持按插入順序遍歷

2. 哈希值不變性

// TreeNode既保持hash值又維持鏈表順序
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    return new Node<>(p.hash, p.key, p.value, next);
}

轉(zhuǎn)換過程中始終保持:

  • 鍵的hashCode不變
  • 鍵對象的equals()不變
  • 值對象引用不變

3、線程安全(在持有鎖的情況下進行)

4、異常處理機制(可進行回滾)

1. 轉(zhuǎn)換失敗回滾

try {
    treeifyBin(tab, hash);
} catch (Throwable t) {
    tab[index] = originalHead; // 回退到原鏈表
    throw t;
}

2. 內(nèi)存溢出防護

// TreeNode構(gòu)造時檢查內(nèi)存
if (remaining < treeNodeSpace) {
    untreeify(); // 立即退化為鏈表
    return;
}

HashMap的轉(zhuǎn)換安全機制通過精細的鎖控制、結(jié)構(gòu)隔離和狀態(tài)校驗,在保證性能的同時實現(xiàn)了線程安全和數(shù)據(jù)一致性。

這種設計體現(xiàn)了Java集合框架在高并發(fā)場景下的工程智慧,也是為什么HashMap能成為最常用的數(shù)據(jù)結(jié)構(gòu)之一的關(guān)鍵所在。

5、設計原因

5.1. 性能權(quán)衡

數(shù)學驗證

當n=6時:

  • 鏈表平均查找次數(shù):3次
  • 紅黑樹查找次數(shù):log?6≈2.58次
  • 性能差距不大,但紅黑樹維護成本更高

5.2. 空間局部性

  • 鏈表節(jié)點內(nèi)存連續(xù)訪問更友好
  • 紅黑樹的樹節(jié)點結(jié)構(gòu)更復雜:
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 父節(jié)點指針
    TreeNode<K,V> left;    // 左子樹指針
    TreeNode<K,V> right;   // 右子樹指針
    TreeNode<K,V> prev;    // 前驅(qū)節(jié)點(仍保留鏈表結(jié)構(gòu))
    boolean red;          // 顏色標記
}

5.3. 實際測試數(shù)據(jù)

在Java標準庫的基準測試中:

  • 節(jié)點數(shù)=6時,鏈表比紅黑樹快約15%
  • 內(nèi)存占用減少約40%

6、常見誤區(qū)

1、誤區(qū):"紅黑樹會自動退化為鏈表"

事實:這是HashMap的主動控制行為。

2、誤區(qū):"轉(zhuǎn)換會破壞數(shù)據(jù)"

事實:元素順序和內(nèi)容完全保留。

3、誤區(qū):"節(jié)點數(shù)在7時會頻繁轉(zhuǎn)換"

事實:只有在resize/remove時檢查閾值。

7、實戰(zhàn)建議

監(jiān)控樹節(jié)點比例

// 檢查桶的樹化情況
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Node<?,?>[] table = (Node<?,?>[]) tableField.get(map);

int trees = 0;
for (Node<?,?> node : table) {
    if (node instanceof TreeNode) trees++;
}

優(yōu)化hashCode()

  • 減少哈希碰撞可避免樹化
  • 示例:
// 好的hashCode實現(xiàn)
@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3); 
}

容量規(guī)劃

// 預設足夠大的initialCapacity
new HashMap<>(expectedSize * 2); 

總結(jié)

JDK的這個設計體現(xiàn)了工程上的精妙權(quán)衡:在保持算法理論正確性的同時,針對實際硬件特性和使用場景做出了最優(yōu)實踐選擇。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • springboot集成KoTime的配置過程

    springboot集成KoTime的配置過程

    koTime是一個springboot項目性能分析工具,通過追蹤方法調(diào)用鏈路以及對應的運行時長快速定位性能瓶頸,這篇文章主要介紹了springboot集成KoTime,需要的朋友可以參考下
    2022-06-06
  • MyBatis處理大字段或BLOB、CLOB類型數(shù)據(jù)方式

    MyBatis處理大字段或BLOB、CLOB類型數(shù)據(jù)方式

    這篇文章主要介紹了MyBatis處理大字段或BLOB、CLOB類型數(shù)據(jù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件

    Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件

    這篇文章主要介紹了Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10
  • 將java程序打包成可執(zhí)行文件的實現(xiàn)方式

    將java程序打包成可執(zhí)行文件的實現(xiàn)方式

    本文介紹了將Java程序打包成可執(zhí)行文件的三種方法:手動打包(將編譯后的代碼及JRE運行環(huán)境一起打包),使用第三方打包工具(如Launch4j)和JDK自帶工具(jpackage),每種方法都有其優(yōu)缺點,可根據(jù)實際需求選擇合適的方式
    2025-02-02
  • spring 如何將配置信息注入靜態(tài)變量的方法

    spring 如何將配置信息注入靜態(tài)變量的方法

    本篇文章主要介紹了spring 如何將配置信息注入靜態(tài)變量的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • mybatis 事務回滾配置操作

    mybatis 事務回滾配置操作

    這篇文章主要介紹了mybatis 事務回滾配置操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Netty進階之EventExecutorGroup源碼詳解

    Netty進階之EventExecutorGroup源碼詳解

    這篇文章主要介紹了Netty進階之EventExecutorGroup源碼詳解,EventExecutorGroup繼承了JDK的ScheduledExecutroService,那么它就擁有了執(zhí)行定時任務,執(zhí)行提交的普通任務,需要的朋友可以參考下
    2023-11-11
  • @CacheEvict注解,清除緩存方式

    @CacheEvict注解,清除緩存方式

    這篇文章主要介紹了@CacheEvict注解,清除緩存方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java常用類之比較器的使用詳解

    Java常用類之比較器的使用詳解

    這篇文章主要為大家詳細介紹了Java中比較器的相關(guān)資料,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2022-11-11
  • 關(guān)于springBoot yml文件的list讀取問題總結(jié)(親測)

    關(guān)于springBoot yml文件的list讀取問題總結(jié)(親測)

    這篇文章主要介紹了關(guān)于springBoot yml文件的list讀取問題總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評論

定南县| 元阳县| 赞皇县| 财经| 温州市| 吐鲁番市| 犍为县| 百色市| 图们市| 哈巴河县| 武穴市| 河北省| 宜州市| 泰来县| 江川县| 襄汾县| 监利县| 永善县| 鲜城| 泰和县| 武威市| 恩施市| 内江市| 伽师县| 台北市| 海兴县| 晋中市| 瓦房店市| 潍坊市| 商丘市| 桓台县| 海阳市| 安阳市| 苍南县| 高淳县| 林西县| 武强县| 新和县| 南江县| 荆门市| 遂昌县|