HashMap源碼淺入深出(一)

簡(jiǎn)書(shū) 加薪貓
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處枪向,謝謝!
這一系列主要介紹HashMap(1.8)彼硫,記錄也是分享吃溅,歡迎討論

0.HashMap 結(jié)構(gòu)

HashMap 的數(shù)據(jù)存在哪里溶诞?數(shù)據(jù)結(jié)構(gòu)是什么?

1.HashMap所有的key-value,存在一個(gè)全局變量Node<K,V>[] table里面决侈。

2.Node<K,V> 這個(gè)的結(jié)構(gòu)具體看代碼

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

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

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

hash用來(lái)存儲(chǔ)這個(gè)Node中key經(jīng)過(guò)HashMap的hash(K key)方法計(jì)算后得出的螺垢,key、value不說(shuō)了赖歌,next存儲(chǔ)一下個(gè)Node節(jié)點(diǎn)枉圃。HashMap是數(shù)組+鏈表的形式存儲(chǔ)的,當(dāng)然這個(gè)Node的子類還有TreeNode庐冯,這個(gè)我們之后再說(shuō)

哈希值是否有重復(fù)孽亲?為什么要用鏈表存儲(chǔ)?

按理說(shuō)哈希值是不會(huì)有重復(fù)的展父,java Object類中的hashCode方法使用類的地址轉(zhuǎn)int返劲,保證了hash值的唯一性,雖說(shuō)哈希值不會(huì)重復(fù)栖茉,但是在存儲(chǔ)時(shí)我們還是會(huì)發(fā)生沖突的篮绿,具體我們可以看下面的介紹,用鏈表存儲(chǔ)就是為了解決沖突問(wèn)題的吕漂,具體可以仔細(xì)研究1.1.2節(jié)亲配。其次1.8不僅用鏈表,當(dāng)鏈表長(zhǎng)度超過(guò)默認(rèn)值(8)時(shí),HashMap還會(huì)把這個(gè)鏈表轉(zhuǎn)為紅黑樹(shù)吼虎,這也是為了提升查找效率犬钢。

正文

HashMap源碼中最有用,最值得看的就是resize()擴(kuò)容方法思灰,直接去看resize()方法肯定是一頭霧水玷犹。

所以這里是從我們最常用的方法一步步去分享。

1. get(Object key)

直接上代碼

public V get(Object key) {
            Node e;
            return(e = getNode(hash(key),key)) ==null?null: e.value;
}

get方法里面主要就是hash 和 getNode

1.1 hash(Object key)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

1.1.1 hashCode

所有對(duì)象都會(huì)有也是必須有hashCode()方法的官辈,這也就是為什么HashMap的Key要求必須是對(duì)象的原因(不可用基本類型,int,long,等等)遍坟。當(dāng)然Value也是要求必須是對(duì)象的拳亿,為什么就待日后再說(shuō)。

這是Object類里面hashCode()方法愿伴。

 public native int hashCode();

一個(gè)不常見(jiàn)的關(guān)鍵詞 native肺魁,使用native關(guān)鍵字說(shuō)明這個(gè)方法是原生函數(shù),也就是這個(gè)方法是用C/C++語(yǔ)言實(shí)現(xiàn)的隔节。鹅经。。后面的不想說(shuō)啦怎诫,既然是C系列那么就不在在下的關(guān)注之中了瘾晃,我們回到源碼Object對(duì)這個(gè)函數(shù)的描寫(xiě)

 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * 返回對(duì)象的哈希值,這個(gè)方法是為了給哈希表提供幫助的(也就是這次講的HashMap)
 * <p>
 * The general contract of {@code hashCode} is:
 * 約束是
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 *     在一個(gè)java應(yīng)用執(zhí)行中幻妓,對(duì)同一個(gè)對(duì)象多次調(diào)用 hashCode()方法蹦误,必須返回相同的整形,
 *     這個(gè)前提是在對(duì)象的比較中肉津,沒(méi)有任何信息被修改.相同程序在多次分別執(zhí)行時(shí)强胰,是不需要相同的
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 *     如果兩個(gè)對(duì)象調(diào)用equals()方法是相等的,那么調(diào)用hashCode方法的返回也是相同的
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code hashCode} method on each of the
 *     two objects must produce distinct integer results.  However, the
 *     programmer should be aware that producing distinct integer results
 *     for unequal objects may improve the performance of hash tables.
 *     兩個(gè)不相等的對(duì)象妹沙,(不)要求hashCode相同偶洋,但是程序猿需要知道,給不相等對(duì)象提供不同的
 *     hash值有利于hash表的查詢
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java? programming language.)
 * 呵呵距糖,Object自己的hashCode方法在不同的對(duì)象上返回不同的整形
 *(這是依賴內(nèi)部地址轉(zhuǎn)換為整形來(lái)實(shí)現(xiàn)玄窝,但是我們重寫(xiě)這個(gè)方法的時(shí)候不要求這樣~)
 * 
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode

1.1.2 >>>

Object.hashCode()已經(jīng)返回了這個(gè)對(duì)象的hash值,那么為什么HashMap里面還有有個(gè)hash方法呢悍引?

(h = key.hashCode()) ^ (h >>> 16)

這個(gè)操作的高度概括就是讓Key的哈希值高位參與運(yùn)算哆料。

為什么要高位參與運(yùn)算呢?

我們算出來(lái)的哈希值是一個(gè)int型吗铐,2進(jìn)制32位帶符號(hào)的int是-2147483648~2147483648东亦,其實(shí)很難會(huì)發(fā)生碰撞(上面也說(shuō)到了,Object提供的hashCode方法算出來(lái)不同對(duì)象的哈希值是不會(huì)有重復(fù)的),如果我們直接使用哈希值作為數(shù)組下標(biāo)訪問(wèn)的話典阵,內(nèi)存是吃不消的奋渔,所以這個(gè)算出來(lái)的哈希值之后會(huì)和 當(dāng)前HashMap的大小做取模運(yùn)算得到的余數(shù) 當(dāng)前HashMap的大小-1 做與運(yùn)算作為下標(biāo)

p = tab[index = (n - 1) & hash]

這一段是之后put方法中使用獲得下標(biāo)的語(yǔ)句,這個(gè)我們之后再講壮啊,根據(jù)上面的代碼嫉鲸,我們?cè)倏础ashMap的默認(rèn)大小是2^4=16,換算成32位的樣式就是

0000 0000 0000 0000 0000 0000 0001 0000 -->16

0000 0000 0000 0000 0000 0000 0000 1111 -->15

當(dāng)我們拿著15去做與運(yùn)算的時(shí)候

從0000 0000 0000 0000 0000 0000 0000 0000

到1111 1111 1111 1111 1111 1111 1111 0000

所有低四位為0的對(duì)象歹啼,在這個(gè)HashMap中都會(huì)存到下標(biāo)為0的對(duì)象里面玄渗,不考慮擴(kuò)容等問(wèn)題,這樣的HashMap(1.8之前)他的查找效率就跟鏈表一樣狸眼,即使1.8將鏈表大小超過(guò)8的鏈表轉(zhuǎn)為紅黑樹(shù)藤树,這也不是HashMap的設(shè)計(jì)初衷。

但是我們將算出來(lái)的哈希值右移16位后取異或拓萌,那么就當(dāng)前大小16的HashMap來(lái)說(shuō)岁钓,參與運(yùn)算的就不只是0 ~ 3位,是0 ~ 3和16 ~ 19位共同的計(jì)算結(jié)果微王,這個(gè)操作使我們的分布更加均勻屡限。

而我們的HashMap的大小默認(rèn)值是16也就是2^4,而有符號(hào)int標(biāo)志范圍炕倘。

順便一提的是這也是為什么我們HashMap的大小必須2的冪次方钧大,因?yàn)檫@樣,大小-1正好是地位掩碼罩旋。

1.2 getNode(int hash,Object key)

以上拓型,我們知道了在HashMap中哈希值的計(jì)算方式,下面我們要討論的是取到hash值之后瘸恼,我們要怎么去取到具體的Value

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

我們已經(jīng)知道了劣挫,HashMap,最底層的數(shù)據(jù)結(jié)構(gòu)是Node<K,V>[],其實(shí)這段代碼蠻好懂的东帅,就是幾個(gè)條件压固,唯一值得注意的地方就是,我們?cè)诰唧w判斷的時(shí)候靠闭,首先判斷((k = e.key) == key),其次我們還要判斷(key != null && key.equals(k))帐我,==和equals的區(qū)別就不贅述了。

從一個(gè)get方法我們也可以看出愧膀,如果我們想用自己新建的一個(gè)類作為HashMap的key,我們一定要正確的重寫(xiě)這個(gè)類的hashCode()和equals()方法拦键,不然最終的結(jié)果可能并不是我們想要的。

這段代碼里面還有與之前JDK版本相比最大的區(qū)別就是引入了紅黑樹(shù)檩淋,這段代碼也可以看到芬为,如果我們這個(gè)節(jié)點(diǎn)是TreeNode的話萄金,我們會(huì)使用TreeNode的getTreeNode(hash,key)的方法獲得我們想要的Node節(jié)點(diǎn),如果不是TreeNode的話媚朦,我們會(huì)遍歷鏈表氧敢。

今天先到這里~

我是加薪貓
技術(shù)是加薪的前提,生活是加薪的意義
技術(shù)等于生活

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末询张,一起剝皮案震驚了整個(gè)濱河市孙乖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌份氧,老刑警劉巖唯袄,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜗帜,居然都是意外死亡恋拷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門钮糖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梅掠,“玉大人酌住,你說(shuō)我怎么就攤上這事店归。” “怎么了酪我?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵消痛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我都哭,道長(zhǎng)秩伞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任欺矫,我火速辦了婚禮纱新,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘穆趴。我一直安慰自己脸爱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布未妹。 她就那樣靜靜地躺著簿废,像睡著了一般。 火紅的嫁衣襯著肌膚如雪络它。 梳的紋絲不亂的頭發(fā)上族檬,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音化戳,去河邊找鬼单料。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的看尼。 我是一名探鬼主播递鹉,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼藏斩!你這毒婦竟也來(lái)了躏结?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狰域,失蹤者是張志新(化名)和其女友劉穎媳拴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兆览,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屈溉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抬探。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子巾。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖小压,靈堂內(nèi)的尸體忽然破棺而出线梗,到底是詐尸還是另有隱情,我是刑警寧澤怠益,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布仪搔,位于F島的核電站,受9級(jí)特大地震影響蜻牢,放射性物質(zhì)發(fā)生泄漏烤咧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一抢呆、第九天 我趴在偏房一處隱蔽的房頂上張望煮嫌。 院中可真熱鬧,春花似錦抱虐、人聲如沸昌阿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宝泵。三九已至,卻和暖如春轩娶,著一層夾襖步出監(jiān)牢的瞬間儿奶,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工鳄抒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闯捎,地道東北人椰弊。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瓤鼻,于是被迫代替她去往敵國(guó)和親秉版。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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

  • HashMap 是 Java 面試必考的知識(shí)點(diǎn)茬祷,面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度清焕。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,667評(píng)論 9 107
  • 謝謝自己當(dāng)初的堅(jiān)持 謝謝沒(méi)有在最后一刻松口 沒(méi)有沖動(dòng)就去做一個(gè)決定 也謝謝你當(dāng)初的堅(jiān)持 謝謝沒(méi)有在最后還堅(jiān)持自己的...
    厭世時(shí)令閱讀 110評(píng)論 0 0
  • 薰衣草,美麗祭犯、浪漫秸妥、芳香、典雅沃粗,被譽(yù)為 “香草之王”粥惧。薰衣草田,紫色一片最盅,令人心馳神往突雪,如癡如醉。 薰衣草觀賞最佳...
    LOVE薰衣草閱讀 1,785評(píng)論 0 2
  • 那些年,我總是獨(dú)自一人坐在老屋堂屋的木質(zhì)沙發(fā)上盼产,看著夕陽(yáng)從半掩的門縫中緩緩地向屋內(nèi)延伸饵婆,直到它移到了對(duì)著正門...
    蝶舞天涯閱讀 477評(píng)論 0 7