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

Java?HashMap從源碼到核心機(jī)制實(shí)現(xiàn)原理深度解析

 更新時(shí)間:2026年01月30日 09:25:37   作者:Leo?July  
HashMap是 Java 集合框架中最常用的數(shù)據(jù)結(jié)構(gòu)之一,基于哈希表(Hash Table)實(shí)現(xiàn),下面這篇文章主要介紹了Java?HashMap從源碼到核心機(jī)制實(shí)現(xiàn)原理的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

作為Java開發(fā)中最常用的集合類之一,HashMap以其高效的鍵值對(duì)存取能力成為日常開發(fā)的“標(biāo)配”,但多數(shù)開發(fā)者僅停留在“會(huì)用”層面,對(duì)其底層實(shí)現(xiàn)、擴(kuò)容機(jī)制、線程安全等核心問(wèn)題一知半解。本文將從數(shù)據(jù)結(jié)構(gòu)、核心機(jī)制、源碼解析三個(gè)維度,徹底拆解HashMap的實(shí)現(xiàn)原理,結(jié)合JDK 8的核心優(yōu)化點(diǎn),幫你從“使用”走向“理解”。

一、HashMap核心定位與設(shè)計(jì)目標(biāo)

HashMap是基于哈希表實(shí)現(xiàn)的Map接口實(shí)現(xiàn)類,核心特點(diǎn):

  • 允許keyvaluenull(Hashtable不允許);
  • 無(wú)序(存儲(chǔ)順序與插入順序無(wú)關(guān));
  • JDK 8前采用“數(shù)組+鏈表”,JDK 8引入“紅黑樹”優(yōu)化鏈表過(guò)長(zhǎng)問(wèn)題;
  • 非線程安全(多線程操作可能導(dǎo)致死循環(huán)、數(shù)據(jù)丟失);
  • 查找、插入、刪除的平均時(shí)間復(fù)雜度為O(1),最壞情況(哈希沖突嚴(yán)重)JDK 7為O(n),JDK 8優(yōu)化為O(logn)。

二、HashMap核心數(shù)據(jù)結(jié)構(gòu)

1. 基礎(chǔ)結(jié)構(gòu):數(shù)組(桶)+ 鏈表 + 紅黑樹

HashMap的底層核心是哈希桶數(shù)組Node[] table),每個(gè)數(shù)組元素(桶)對(duì)應(yīng)一個(gè)鏈表/紅黑樹,用于解決哈希沖突:

  • 哈希桶數(shù)組:存儲(chǔ)數(shù)據(jù)的核心容器,默認(rèn)初始容量為16(DEFAULT_INITIAL_CAPACITY);
  • 鏈表:當(dāng)多個(gè)key的哈希值映射到同一個(gè)桶時(shí),通過(guò)鏈表串聯(lián)(JDK 7頭插法,JDK 8尾插法,解決并發(fā)死循環(huán)問(wèn)題);
  • 紅黑樹:當(dāng)鏈表長(zhǎng)度≥8且數(shù)組容量≥64時(shí),鏈表轉(zhuǎn)為紅黑樹(鏈表長(zhǎng)度≤6時(shí)回退為鏈表),降低查詢耗時(shí)。

2. 核心節(jié)點(diǎn)類

JDK 8中HashMap的節(jié)點(diǎn)分為兩種:

// 普通鏈表節(jié)點(diǎn)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;    // key的哈希值(經(jīng)過(guò)擾動(dòng)處理)
    final K key;       // 鍵
    V value;           // 值
    Node<K,V> next;    // 下一個(gè)節(jié)點(diǎn)引用

    Node(int hash, K key, V value, Node<K,V> next) { ... }
}

// 紅黑樹節(jié)點(diǎn)(繼承自Node)
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 父節(jié)點(diǎn)
    TreeNode<K,V> left;    // 左子節(jié)點(diǎn)
    TreeNode<K,V> right;   // 右子節(jié)點(diǎn)
    TreeNode<K,V> prev;    // 前驅(qū)節(jié)點(diǎn)
    boolean red;           // 紅黑樹顏色標(biāo)記
    TreeNode(int hash, K key, V value, Node<K,V> next) { ... }
}

三、HashMap核心機(jī)制解析

1. 哈希計(jì)算與尋址:如何定位key的存儲(chǔ)位置

HashMap的核心是通過(guò)哈希算法將key映射到數(shù)組的指定位置,分為兩步:

(1)哈希值計(jì)算(擾動(dòng)函數(shù))

為了減少哈希沖突,JDK 8對(duì)key的hashCode()進(jìn)行“擾動(dòng)處理”,混合高位和低位特征:

static final int hash(Object key) {
    int h;
    // key為null時(shí)hash為0,所以HashMap允許key為null
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 取key的hashCode()(32位整數(shù));
  • 將高16位與低16位異或(^),讓高位特征參與尋址,降低哈希沖突概率。

(2)數(shù)組尋址

通過(guò)哈希值計(jì)算key在數(shù)組中的索引:

// n為數(shù)組長(zhǎng)度(必須是2的冪)
int index = (n - 1) & hash;
  • 數(shù)組長(zhǎng)度n設(shè)計(jì)為2的冪,使得n-1的二進(jìn)制全為1,等價(jià)于hash % n但效率更高;
  • n不是2的冪,(n-1) & hash會(huì)導(dǎo)致部分索引無(wú)法命中,浪費(fèi)數(shù)組空間。

2. 擴(kuò)容機(jī)制(resize())

當(dāng)HashMap的元素?cái)?shù)量(size)超過(guò)負(fù)載因子×數(shù)組容量時(shí),觸發(fā)擴(kuò)容,核心規(guī)則:

  • 負(fù)載因子默認(rèn)值:0.75(DEFAULT_LOAD_FACTOR),平衡空間利用率和哈希沖突;
  • 擴(kuò)容規(guī)則:數(shù)組容量翻倍(2倍),重新計(jì)算所有節(jié)點(diǎn)的索引并遷移;
  • 擴(kuò)容優(yōu)化(JDK 8):由于容量翻倍,節(jié)點(diǎn)新索引要么不變,要么為原索引+舊容量,無(wú)需重新計(jì)算哈希,提升擴(kuò)容效率。

擴(kuò)容核心邏輯(簡(jiǎn)化版源碼)

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold; // 擴(kuò)容閾值(負(fù)載因子×容量)
    int newCap, newThr = 0;

    if (oldCap > 0) {
        // 超過(guò)最大容量(2^30),不再擴(kuò)容
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 容量翻倍,閾值也翻倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
            newThr = oldThr << 1;
        }
    }
    // 初始化容量(首次put時(shí))
    else if (oldThr > 0) newCap = oldThr;
    else {
        newCap = DEFAULT_INITIAL_CAPACITY; // 16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 12
    }

    // 創(chuàng)建新數(shù)組,遷移舊節(jié)點(diǎn)
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 單個(gè)節(jié)點(diǎn),直接遷移
                if (e.next == null) newTab[e.hash & (newCap - 1)] = e;
                // 紅黑樹節(jié)點(diǎn),拆分遷移
                else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                // 鏈表節(jié)點(diǎn),按新索引拆分(JDK 8優(yōu)化點(diǎn))
                else {
                    Node<K,V> loHead = null, loTail = null; // 索引不變的節(jié)點(diǎn)
                    Node<K,V> hiHead = null, hiTail = null; // 索引=原索引+舊容量的節(jié)點(diǎn)
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) { // 索引不變
                            if (loTail == null) loHead = e;
                            else loTail.next = e;
                            loTail = e;
                        } else { // 索引=j+oldCap
                            if (hiTail == null) hiHead = e;
                            else hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

3. 紅黑樹轉(zhuǎn)換規(guī)則

JDK 8引入紅黑樹的核心目的是解決“鏈表過(guò)長(zhǎng)導(dǎo)致查詢效率低”的問(wèn)題,轉(zhuǎn)換條件嚴(yán)格:

  • 鏈表轉(zhuǎn)紅黑樹
    1. 鏈表長(zhǎng)度≥8;
    2. 數(shù)組容量≥64(若數(shù)組容量<64,先擴(kuò)容而非轉(zhuǎn)紅黑樹);
  • 紅黑樹轉(zhuǎn)鏈表:鏈表長(zhǎng)度≤6(避免頻繁轉(zhuǎn)換);
  • 閾值設(shè)計(jì)原因:基于泊松分布,鏈表長(zhǎng)度≥8的概率僅0.00000006,幾乎是小概率事件,避免過(guò)度優(yōu)化。

四、核心方法源碼解析:put()

put方法是HashMap最核心的方法,完整體現(xiàn)了“哈希計(jì)算→尋址→沖突處理→擴(kuò)容”的全流程,JDK 8核心邏輯:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 1. 數(shù)組未初始化/長(zhǎng)度為0,先擴(kuò)容
    if ((tab = table) == null || (n = tab.length) == 0) {
        n = (tab = resize()).length;
    }
    // 2. 計(jì)算索引,若桶為空,直接創(chuàng)建新節(jié)點(diǎn)
    if ((p = tab[i = (n - 1) & hash]) == null) {
        tab[i] = newNode(hash, key, value, null);
    } else {
        Node<K,V> e; K k;
        // 3. 桶中節(jié)點(diǎn)的key與當(dāng)前key相同,直接覆蓋value
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;
        }
        // 4. 桶中是紅黑樹節(jié)點(diǎn),調(diào)用紅黑樹插入方法
        else if (p instanceof TreeNode) {
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        }
        // 5. 桶中是鏈表節(jié)點(diǎn),遍歷鏈表
        else {
            for (int binCount = 0; ; ++binCount) {
                // 鏈表尾部,插入新節(jié)點(diǎn)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 鏈表長(zhǎng)度≥8,觸發(fā)紅黑樹轉(zhuǎn)換
                    if (binCount >= TREEIFY_THRESHOLD - 1) {
                        treeifyBin(tab, hash);
                    }
                    break;
                }
                // 找到相同key,跳出循環(huán)(后續(xù)覆蓋value)
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                    break;
                }
                p = e;
            }
        }
        // 6. 存在相同key,覆蓋value并返回舊值
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null) {
                e.value = value;
            }
            afterNodeAccess(e); // 空方法,LinkedHashMap重寫
            return oldValue;
        }
    }
    ++modCount; // 快速失?。╢ail-fast)標(biāo)記
    // 7. 元素?cái)?shù)量超過(guò)閾值,觸發(fā)擴(kuò)容
    if (++size > threshold) {
        resize();
    }
    afterNodeInsertion(evict); // 空方法,LinkedHashMap重寫
    return null;
}

五、HashMap的線程安全問(wèn)題

1. 核心問(wèn)題

HashMap非線程安全,多線程并發(fā)操作會(huì)導(dǎo)致:

  • JDK 7死循環(huán):擴(kuò)容時(shí)頭插法導(dǎo)致鏈表成環(huán),查詢時(shí)無(wú)限循環(huán);
  • 數(shù)據(jù)丟失/覆蓋:多線程同時(shí)put,可能導(dǎo)致節(jié)點(diǎn)覆蓋;
  • 擴(kuò)容丟失數(shù)據(jù):多線程擴(kuò)容時(shí),節(jié)點(diǎn)遷移過(guò)程中數(shù)據(jù)丟失。

2. 替代方案

  • ConcurrentHashMap:JDK 8采用“CAS+分段鎖”實(shí)現(xiàn)線程安全,性能遠(yuǎn)優(yōu)于Hashtable;
  • Collections.synchronizedMap:通過(guò)包裝類加全局鎖,性能較差;
  • Hashtable:方法加synchronized,全局鎖,性能最差(不推薦)。

六、實(shí)戰(zhàn)/面試高頻要點(diǎn)

1. 為什么HashMap的容量必須是2的冪?

  • 尋址時(shí)(n-1) & hash等價(jià)于hash % n,位運(yùn)算效率更高;
  • 擴(kuò)容時(shí)節(jié)點(diǎn)新索引僅兩種可能(原索引/原索引+舊容量),無(wú)需重新計(jì)算哈希,提升擴(kuò)容效率;
  • 減少哈希沖突,讓索引分布更均勻。

2. 負(fù)載因子為什么默認(rèn)是0.75?

  • 0.75是時(shí)間和空間的平衡值:
    • 負(fù)載因子過(guò)高:哈希沖突概率增加,鏈表/紅黑樹變長(zhǎng),查詢效率降低;
    • 負(fù)載因子過(guò)低:數(shù)組空間利用率低,擴(kuò)容頻繁,性能開銷大。

3. JDK 7 vs JDK 8 HashMap核心差異

特性JDK 7JDK 8
數(shù)據(jù)結(jié)構(gòu)數(shù)組+鏈表數(shù)組+鏈表+紅黑樹
插入方式頭插法(并發(fā)死循環(huán))尾插法(解決死循環(huán))
哈希計(jì)算4次位運(yùn)算+5次異或1次異或(簡(jiǎn)化擾動(dòng))
擴(kuò)容后索引重新計(jì)算僅兩種可能(優(yōu)化效率)
失敗機(jī)制fail-fastfail-fast

七、總結(jié)

HashMap的核心設(shè)計(jì)圍繞“高效哈希尋址”展開,JDK 8的紅黑樹優(yōu)化、尾插法、擴(kuò)容優(yōu)化等,都是為了在哈希沖突場(chǎng)景下保證性能:

  1. 數(shù)據(jù)結(jié)構(gòu):數(shù)組是基礎(chǔ),鏈表解決沖突,紅黑樹優(yōu)化長(zhǎng)鏈表;
  2. 核心機(jī)制:哈希擾動(dòng)減少?zèng)_突,2次冪容量提升尋址效率,0.75負(fù)載因子平衡時(shí)空;
  3. 線程安全:避免多線程直接操作,優(yōu)先使用ConcurrentHashMap;
  4. 實(shí)戰(zhàn)建議:初始化時(shí)指定容量(避免頻繁擴(kuò)容),key盡量用不可變類型(如String、Integer),保證hashCode穩(wěn)定。

理解HashMap的實(shí)現(xiàn)原理,不僅能應(yīng)對(duì)面試,更能在高并發(fā)、大數(shù)據(jù)量場(chǎng)景下合理使用HashMap,避免性能問(wèn)題和線上故障。

到此這篇關(guān)于Java HashMap從源碼到核心機(jī)制實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Java HashMap實(shí)現(xiàn)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決SpringMVC獲取請(qǐng)求參數(shù)亂碼問(wèn)題

    解決SpringMVC獲取請(qǐng)求參數(shù)亂碼問(wèn)題

    在使用SpringMVC和thymeleaf進(jìn)行請(qǐng)求參數(shù)處理時(shí),可能會(huì)遇到亂碼問(wèn)題,對(duì)于GET方法亂碼,可通過(guò)修改Tomcat的server.xml文件,添加URIEncoding="UTF-8"解決,而POST方法亂碼,則需在web.xml配置SpringMVC提供的過(guò)濾器
    2024-11-11
  • Intellij Mybatis連接Mysql數(shù)據(jù)庫(kù)

    Intellij Mybatis連接Mysql數(shù)據(jù)庫(kù)

    最近在搞android的項(xiàng)目,在開發(fā)過(guò)程中遇到了好多問(wèn)題,今天小編給大家說(shuō)下mybatis連接MySQL數(shù)據(jù)庫(kù)的方法,感興趣的朋友跟著小編一起學(xué)習(xí)吧
    2016-10-10
  • Java設(shè)計(jì)模式中的裝飾器模式簡(jiǎn)析

    Java設(shè)計(jì)模式中的裝飾器模式簡(jiǎn)析

    這篇文章主要介紹了Java設(shè)計(jì)模式中的裝飾器模式簡(jiǎn)析,裝飾模式能夠?qū)崿F(xiàn)動(dòng)態(tài)的為對(duì)象添加功能,是從一個(gè)對(duì)象外部來(lái)給對(duì)象添加功能,通常給對(duì)象添加功能,要么直接修改對(duì)象添加相應(yīng)的功能,要么派生對(duì)應(yīng)的子類來(lái)擴(kuò)展,抑或是使用對(duì)象組合的方式,需要的朋友可以參考下
    2023-12-12
  • Spring超詳細(xì)講解創(chuàng)建BeanDefinition流程

    Spring超詳細(xì)講解創(chuàng)建BeanDefinition流程

    Spring在初始化過(guò)程中,將xml中定義的對(duì)象解析到了BeanDefinition對(duì)象中,我們有必要了解一下BeanDefinition的內(nèi)部結(jié)構(gòu),有助于我們理解Spring的初始化流程
    2022-06-06
  • springboot如何開啟和關(guān)閉kafka消費(fèi)

    springboot如何開啟和關(guān)閉kafka消費(fèi)

    在Kafka消費(fèi)者中,通過(guò)關(guān)閉自動(dòng)消費(fèi)配置,使用自定義容器工廠,并在消費(fèi)監(jiān)聽器上設(shè)置id,可以手動(dòng)控制消費(fèi)的開啟和關(guān)閉,這是根據(jù)個(gè)人經(jīng)驗(yàn)總結(jié)的方法,旨在幫助其他開發(fā)者
    2024-12-12
  • Java如何計(jì)算兩個(gè)時(shí)間段內(nèi)的工作日天數(shù)

    Java如何計(jì)算兩個(gè)時(shí)間段內(nèi)的工作日天數(shù)

    這篇文章主要介紹了Java如何計(jì)算兩個(gè)時(shí)間段內(nèi)的工作日天數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • RabbitMQ?Stream插件使用案例代碼

    RabbitMQ?Stream插件使用案例代碼

    這篇文章主要介紹了RabbitMQ?Stream插件使用案例代碼,2.4版為RabbitMQ流插件引入了對(duì)RabbitMQStream插件Java客戶端的初始支持,需要的朋友可以參考下
    2024-04-04
  • java實(shí)現(xiàn)小球碰撞功能

    java實(shí)現(xiàn)小球碰撞功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)小球碰撞功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • 快速上手Mybatis-plus結(jié)構(gòu)構(gòu)建過(guò)程

    快速上手Mybatis-plus結(jié)構(gòu)構(gòu)建過(guò)程

    這篇文章主要介紹了快速上手Mybatis-plus結(jié)構(gòu)構(gòu)建過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • 解決Java變異出現(xiàn)錯(cuò)誤No enclosing instance of type XXX is accessible

    解決Java變異出現(xiàn)錯(cuò)誤No enclosing instance of type XXX is accessible

    這牌你文章主要給大家分享解決Java變異出現(xiàn)錯(cuò)誤,具體的饑餓絕方案請(qǐng)看下面文章的內(nèi)容,需要的朋友可以參考一下,希望能幫助到你
    2021-09-09

最新評(píng)論

营山县| 大丰市| 花莲市| 芷江| 雅江县| 德州市| 阿克陶县| 老河口市| 西华县| 溧水县| 东丰县| 镇雄县| 德昌县| 寿光市| 托克逊县| 云林县| 肇源县| 阿巴嘎旗| 延川县| 芦山县| 文山县| 岳池县| 桃江县| 华亭县| 维西| 广宗县| 峨边| 平山县| 普安县| 金湖县| 新竹县| 米易县| 洪洞县| 西林县| 逊克县| 吐鲁番市| 图木舒克市| 潞城市| 虞城县| 惠安县| 佛冈县|