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

JDK8 HashMap紅黑樹退化為鏈表的機(jī)制方式

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

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

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

如下圖所示:

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

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

如下圖所示:

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

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

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

2、Fail-Fast機(jī)制

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

2.1、核心作用

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

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

2.2、實(shí)現(xiàn)原理

1. 關(guān)鍵變量

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

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

2. 工作流程

2.3、觸發(fā)場景

1. 迭代時(shí)修改集合

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ā)主線程迭代時(shí)fail-fast
}).start();

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

2.4、實(shí)現(xiàn)細(xì)節(jié)

1. 修改計(jì)數(shù)更新點(diǎn)

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

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

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

2. 迭代器檢查點(diǎn)

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

2.5、對比

2.6、注意事項(xiàng)

1.正確刪除元素

// 錯(cuò)誤方式(觸發(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機(jī)制雖然會給開發(fā)者帶來一些"麻煩",但它有效地預(yù)防了更危險(xiǎn)的隱性數(shù)據(jù)一致性問題,是Java集合框架健壯性的重要保障。

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

3、核心結(jié)論

在JDK8+的HashMap中:

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

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

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

HashMap在JDK8引入的紅黑樹轉(zhuǎn)換機(jī)制包含嚴(yán)格的安全保障措施,確保在鏈表與紅黑樹相互轉(zhuǎn)換時(shí)不會破壞數(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);
}

雙重校驗(yàn)保障

單鏈表長度≥8

哈希表容量≥64

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

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

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

安全邊界

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

4.2. 轉(zhuǎn)換過程

如下圖所示:

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

關(guān)鍵保障

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

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

如下圖所示:

代碼示例:

// TreeNode.untreeify() 實(shí)現(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é)點(diǎn)
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    return hd;
}

安全保障

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

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

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

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

失效保護(hù)

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

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

1. 雙向鏈表維護(hù)

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

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

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

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

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

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、線程安全(在持有鎖的情況下進(jìn)行)

4、異常處理機(jī)制(可進(jìn)行回滾)

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

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

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

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

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

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

5、設(shè)計(jì)原因

5.1. 性能權(quán)衡

數(shù)學(xué)驗(yàn)證

當(dāng)n=6時(shí):

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

5.2. 空間局部性

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

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

在Java標(biāo)準(zhǔn)庫的基準(zhǔn)測試中:

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

6、常見誤區(qū)

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

事實(shí):這是HashMap的主動控制行為。

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

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

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

事實(shí):只有在resize/remove時(shí)檢查閾值。

7、實(shí)戰(zhàn)建議

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

// 檢查桶的樹化情況
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實(shí)現(xiàn)
@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3); 
}

容量規(guī)劃

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

總結(jié)

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

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

相關(guān)文章

  • springboot集成KoTime的配置過程

    springboot集成KoTime的配置過程

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

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

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

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

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

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

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

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

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

    mybatis 事務(wù)回滾配置操作

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

    Netty進(jìn)階之EventExecutorGroup源碼詳解

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

    @CacheEvict注解,清除緩存方式

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

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

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

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

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

最新評論

太仓市| 中江县| 靖西县| 手游| 黔西县| 五台县| 东明县| 渝北区| 辽阳县| 上杭县| 新昌县| 玛纳斯县| 吴堡县| 肇庆市| 甘泉县| 武功县| 福鼎市| 佛冈县| 扶绥县| 江山市| 徐州市| 六盘水市| 封开县| 阿克陶县| 阿城市| 湖北省| 晴隆县| 固阳县| 大悟县| 花垣县| 辛集市| 米易县| 张家口市| 南陵县| 宣威市| 右玉县| 宝丰县| 胶南市| 濮阳市| 镇江市| 天台县|