HashMap源碼閱讀

1. 什么是HashMap景馁?

image.png
1.1 map的定義

首先你要知道什么是map乘盼,map就是用于存儲(chǔ)鍵值對(duì)(<key,value>)的集合類(lèi)头朱,也可以說(shuō)是一組鍵值對(duì)的映射勘高。

1.2 Map的特點(diǎn)

1.沒(méi)有重復(fù)的 key(一方面帝嗡,key用set保存晶通,所以key必須是唯一,無(wú)序的哟玷;另一方面狮辽,map的取值基本上是通過(guò)key來(lái)獲取value,如果有兩個(gè)相同的key巢寡,計(jì)算機(jī)將不知道到底獲取哪個(gè)對(duì)應(yīng)值喉脖;這時(shí)候有可能會(huì)問(wèn),那為什么我編程時(shí)候可以用put()方法傳入兩個(gè)key值相同的鍵值對(duì)抑月?那是因?yàn)樵创a中树叽,傳入key值相同的鍵值對(duì),將作為覆蓋處理)

2.每個(gè) key 只能對(duì)應(yīng)一個(gè) value, 多個(gè) key 可以對(duì)應(yīng)一個(gè) value(這就是映射的概念谦絮,最經(jīng)典的例子就是射箭题诵,一排射手,一排箭靶层皱,一個(gè)射手只能射中一個(gè)箭靶性锭,而每個(gè)箭靶可能被不同射手射中。這里每個(gè)射手只有一根箭叫胖,不存在三箭齊發(fā)還都中靶這種騷操作草冈。將射手和射中的靶子連線(xiàn),這根線(xiàn)加射手加靶子就是一個(gè)映射)

3.key,value 都可以是任何引用類(lèi)型(包括 null)的數(shù)據(jù)(只能是引用類(lèi)型)

1.3 HashMap

HashMap在JDK1.8中是基于(數(shù)組+鏈表)+紅黑樹(shù)的一個(gè)Map容器

2. HashMap解讀

2.1 靜態(tài)屬性(默認(rèn)值)
   /**
     *  如果沒(méi)有給容量初始化值得話(huà)瓮增,就用這個(gè)作為初始化值怎棱,16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * Map容量最大值
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     *  負(fù)載因子,用來(lái)判斷擴(kuò)容量的
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 當(dāng)Node的長(zhǎng)度達(dá)到8的時(shí)候就轉(zhuǎn)換成紅黑樹(shù)
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 當(dāng)Node的長(zhǎng)度被remove到6的時(shí)候就從紅黑樹(shù)轉(zhuǎn)成鏈表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

   /**
    * 容器可以樹(shù)化的最小容量绷跑。
    *(否則拳恋,如果bin中的節(jié)點(diǎn)太多,則會(huì)調(diào)整表的大小你踩。)
    * 應(yīng)至少為4 * TREEIFY_THRESHOLD诅岩,以避免調(diào)整
    * 大小和樹(shù)化閾值之間的沖突讳苦。
    */
    static final int MIN_TREEIFY_CAPACITY = 64;
2.2 HashMap實(shí)例的屬性
    /**
     * HashMap底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)Node數(shù)組,可以是紅黑樹(shù)樹(shù)吩谦,也可以是鏈表鸳谜,默認(rèn)是鏈表
     */
    transient Node<K,V>[] table;

    /**
     * Map中存儲(chǔ)元素的數(shù)量
     */
    transient int size;

    /**
     * 要調(diào)整大小的下一個(gè)大小值(容量*加載因子)
     * 此外,如果尚未分配表數(shù)組式廷,則此字段保存初始數(shù)組容量
     * put一個(gè)元素進(jìn)Map的時(shí)候咐扭,都會(huì)判斷添加后的size和這個(gè)
     * threshold比較,如果大于了這個(gè)值滑废,就擴(kuò)容蝗肪。
     */
    int threshold;

    /**
     * 哈希表的加載因子
     * 這個(gè)就是為了計(jì)算出threshold的,下面會(huì)說(shuō)
     */
    final float loadFactor;
2.3 HashMap初始化

HashMap總共定義了四個(gè)構(gòu)造函數(shù)蠕趁。

2.3.1 // 傳入初始化容量薛闪,和負(fù)載因子進(jìn)行初始化
    // 傳入初始化容量,和負(fù)載因子
    public HashMap(int initialCapacity, float loadFactor) {
        // 如果初始化容量小于0俺陋,則拋出異常豁延。
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        // 如果大于最大的容量,就設(shè)置成最大容量腊状。
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 如果負(fù)載因子小于0诱咏,或者不是個(gè)數(shù)字則,拋出異常缴挖。
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        // 這里進(jìn)行第一次計(jì)算初始化容量
        this.threshold = tableSizeFor(initialCapacity);
    }

剛剛上面看到了第一次計(jì)算容量用的是tableSizeFor(); 這個(gè)方法袋狞,我們現(xiàn)在來(lái)看一下這個(gè)方法。

   /**
     * 返回的大小一定是2的冪
     * 意思就是映屋,你傳的初始化容量大小苟鸯,取比這個(gè)數(shù)最大的2的n次方的值
     * 打比方:
     * 傳1那么容量就是2的0次方,那么容量就是1
     * 傳入的是2那么就是2的1次方秧荆,那么容量就是2
     * 傳入的是3那么就是2的2次方倔毙,那么容量就是4
     * 傳入的是4那么就是2的3次方,那么容量就是8 
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
2.3.2 傳一個(gè)初始化容量大小初始化
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
2.3.3 默認(rèn)構(gòu)造函數(shù)
 public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }
2.3.4 傳一個(gè)Map進(jìn)行初始化

剛剛介紹的三種構(gòu)造函數(shù)可以看出來(lái)乙濒,都沒(méi)有給HashMap進(jìn)行初始化,但是這個(gè)構(gòu)造函數(shù)卵蛉,會(huì)給出初始化颁股。

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
// 將傳入的Map的值都添加進(jìn)目前初始化的HashMap
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
            // 傳入的Map大小
            int s = m.size();
            if (s > 0) {
                    if (table == null) { 
                        // 如果table是空的就計(jì)算容量,容量大小就是 (size/loadFactor) + 1.0F
                        float ft = ((float)s / loadFactor) + 1.0F;
                        int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                             (int)ft : MAXIMUM_CAPACITY);
                        // 如果容量大于了擴(kuò)容標(biāo)準(zhǔn)傻丝,就計(jì)算新的擴(kuò)容的標(biāo)準(zhǔn)大小甘有。
                        if (t > threshold)
                              threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                // 如果size大于了擴(kuò)容標(biāo)準(zhǔn),調(diào)用進(jìn)行resize()擴(kuò)容
                resize();
            // 循環(huán)put進(jìn)去
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                // 新增元素的方法
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

從上面可以看到主要是兩個(gè)方法葡缰,resize(); 擴(kuò)容方法亏掀,和putVal(); put一個(gè)元素到容器的方法忱反,我們繼續(xù)來(lái)看看putVal();方法
JDK1.7是通過(guò)hash%cap得出存儲(chǔ)數(shù)據(jù)位置,就是hash值模上數(shù)組length得出位置
JDK1.8是通過(guò)容量大小與key值進(jìn)行hash的算法在開(kāi)始的時(shí)候只會(huì)對(duì)低位進(jìn)行計(jì)算滤愕,雖然容量的2進(jìn)制高位一開(kāi)始都是0温算,但是key的2進(jìn)制高位通常是有值的,因此先在hash方法中將key的hashCode右移16位在與自身異或间影,使得高位也可以參與hash注竿,更大程度上減少了碰撞率。先是jdk1.8的圖解

image.png

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果tab是空的魂贬,初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 如果該位置沒(méi)有值巩割,直接new一個(gè)節(jié)點(diǎn),存儲(chǔ)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 如果該位置的有值付燥,并且就是自己的話(huà)宣谈,就覆蓋一下value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 如果該節(jié)點(diǎn)是樹(shù)的話(huà),就用樹(shù)的put方法
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 長(zhǎng)度到了8就轉(zhuǎn)成紅黑樹(shù)
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            // 添加完了以后++size大于了閾值就擴(kuò)容
            resize();
        afterNodeInsertion(evict);
        return null;
    }

小小寫(xiě)了一下键科,發(fā)現(xiàn)寫(xiě)這玩意太廢頭發(fā)了闻丑,所以還是自己理解一下,借鑒一下網(wǎng)上大佬例子好了萝嘁。
JDK1.7 https://blog.csdn.net/xiaokang123456kao/article/details/77503784
JDK1.8 https://www.cnblogs.com/xiaoxi/p/7233201.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梆掸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牙言,更是在濱河造成了極大的恐慌酸钦,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咱枉,死亡現(xiàn)場(chǎng)離奇詭異卑硫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蚕断,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)欢伏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人亿乳,你說(shuō)我怎么就攤上這事硝拧。” “怎么了葛假?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵障陶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我聊训,道長(zhǎng)抱究,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任带斑,我火速辦了婚禮鼓寺,結(jié)果婚禮上勋拟,老公的妹妹穿的比我還像新娘。我一直安慰自己妈候,他們只是感情好敢靡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著州丹,像睡著了一般醋安。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墓毒,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天吓揪,我揣著相機(jī)與錄音,去河邊找鬼所计。 笑死柠辞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的主胧。 我是一名探鬼主播叭首,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踪栋!你這毒婦竟也來(lái)了焙格?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夷都,失蹤者是張志新(化名)和其女友劉穎眷唉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體囤官,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冬阳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了党饮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肝陪。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刑顺,靈堂內(nèi)的尸體忽然破棺而出氯窍,到底是詐尸還是另有隱情,我是刑警寧澤蹲堂,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布荞驴,位于F島的核電站,受9級(jí)特大地震影響贯城,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霹娄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一能犯、第九天 我趴在偏房一處隱蔽的房頂上張望鲫骗。 院中可真熱鬧,春花似錦踩晶、人聲如沸执泰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)术吝。三九已至,卻和暖如春茸苇,著一層夾襖步出監(jiān)牢的瞬間排苍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工学密, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淘衙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓腻暮,卻偏偏與公主長(zhǎng)得像彤守,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哭靖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 目錄結(jié)構(gòu) 導(dǎo)入語(yǔ) HashMap構(gòu)造方法 put()方法解析 addEntry()方法解析 get()方法解析 r...
    使徒行者白閱讀 599評(píng)論 0 49
  • HashMap 源碼閱讀 之前讀過(guò)一些類(lèi)的源碼具垫,近來(lái)發(fā)現(xiàn)都忘了,再讀一遍整理記錄一下试幽。這次讀的是 JDK 11 的...
    趕快跑閱讀 720評(píng)論 0 0
  • HashMap源碼 HashMap 主要用來(lái)存放鍵值對(duì)筝蚕,它基于哈希表的Map接口實(shí)現(xiàn),是常用的Java集合之一抡草。 ...
    夢(mèng)醉_64c0閱讀 323評(píng)論 0 0
  • 背景: 昨天面試udesk, 技術(shù)主管問(wèn)HashMap / ConcurrentHashMap / ThreadL...
    yangguansanyue閱讀 304評(píng)論 0 0
  • “你一直都不是那么幸運(yùn)的人饰及。投胎技術(shù)不過(guò)硬,兌獎(jiǎng)券永遠(yuǎn)只能刮出謝謝康震,喜歡的人總是看不見(jiàn)你燎含。也不知道努力了多久才能...
    Rhymie閱讀 501評(píng)論 0 0