關(guān)于HashMap的源碼解讀一

基于JDK1.8的HashMap源碼中的一些比較有意思的點(diǎn)的解析。

1厢岂、為什么Hash的加載因子是0.75光督?

其實(shí)這一點(diǎn)在源碼的注釋上有說明阳距;其本質(zhì)就是就是空間利用率與查找時(shí)間的一個(gè)折中。
其中涉及到了泊松分布的問題结借,這里就不做過多的解釋(畢竟概率論的東西我也忘得差不多了)筐摘。
如果這個(gè)加載因子太小的話。則空間利用率很低,還沒put幾個(gè)元素就觸發(fā)擴(kuò)容了(擴(kuò)容就會涉及到rehash操作咖熟,開銷比較大)圃酵。
如果加載銀子太大,則hash碰撞的概率很高了馍管。且這時(shí)候郭赐,可能已經(jīng)形成了鏈表了。甚至?xí)|發(fā)鏈表轉(zhuǎn)為樹的操作(在具有良好分布的用戶hashCode的用法中确沸,很少使用樹)捌锭。對查詢效率不利。
而源碼中解釋了為什么0.75最好罗捎。這里貼一部分源碼的說明:
     * Because TreeNodes are about twice the size of regular nodes, we
     * use them only when bins contain enough nodes to warrant use
     * (see TREEIFY_THRESHOLD). And when they become too small (due to
     * removal or resizing) they are converted back to plain bins.  In
     * usages with well-distributed user hashCodes, tree bins are
     * rarely used.  Ideally, under random hashCodes, the frequency of
     * nodes in bins follows a Poisson distribution
     * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
     * parameter of about 0.5 on average for the default resizing
     * threshold of 0.75, although with a large variance because of
     * resizing granularity. Ignoring variance, the expected
     * occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
     * factorial(k)). The first values are:
     *
     * 0:    0.60653066
     * 1:    0.30326533
     * 2:    0.07581633
     * 3:    0.01263606
     * 4:    0.00157952
     * 5:    0.00015795
     * 6:    0.00001316
     * 7:    0.00000094
     * 8:    0.00000006
     * more: less than 1 in ten million
大概翻譯一下:
因?yàn)門reeNode的大小約為常規(guī)節(jié)點(diǎn)的兩倍观谦,所以我們僅在桶(bins)包含足以保證使用的節(jié)點(diǎn)時(shí)才使用它們。-- 反正就是盡量不用到樹桨菜,當(dāng)鏈表長度達(dá)到8的時(shí)候會轉(zhuǎn)換為樹結(jié)構(gòu))
并且當(dāng)它們變得太谢碜础(由于移除或調(diào)整大小)時(shí)倒得,它們會轉(zhuǎn)換回普通bins(鏈表結(jié)構(gòu))泻红。-- 當(dāng)長度又降到6的時(shí)候,就會從樹變成鏈表屎暇,為什么不是小于8就變呢承桥,這里涉及到一個(gè)臨近問題,假設(shè)頻繁的在7個(gè)8之間切換根悼,這會頻繁的鏈轉(zhuǎn)樹凶异,樹轉(zhuǎn)鏈,這樣很不好挤巡。 
在具有良好分布的用戶hashCode的用法中剩彬,很少使用樹。理想情況下矿卑,在隨機(jī)hashCodes下喉恋,bin中節(jié)點(diǎn)的頻率遵循Poisson分布,默認(rèn)調(diào)整大小的參數(shù)平均閾值為0.75 母廷,盡管由于調(diào)整粒度而差異很大轻黑。忽略方差,列表大小k的預(yù)期出現(xiàn)次數(shù)是(exp(-0.5)* pow(0.5琴昆,k)/ * factorial(k))氓鄙。第一個(gè)值是:
     * 0:    0.60653066
     * 1:    0.30326533
     * 2:    0.07581633
     * 3:    0.01263606
     * 4:    0.00157952
     * 5:    0.00015795
     * 6:    0.00001316
     * 7:    0.00000094
     * 8:    0.00000006
     * more: less than 1 in ten million
最后這段話我沒太懂,我的理解是业舍,當(dāng)加載因子是0.75的時(shí)候抖拦,由于hash碰撞而產(chǎn)生的鏈表長度達(dá)到8的概率已經(jīng)很低了升酣。

2、關(guān)于構(gòu)造函數(shù)中的tableSizeFor方法的分析态罪?

/**
     * Returns a power of two size for the given target capacity.
     */
    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;
    }
 該方法到底在做什么呢噩茄?其實(shí)就是返回一個(gè)大于等于用戶指定的初始容量的2次冪數(shù):用戶輸入7,就返回8复颈。用戶輸入10绩聘,就返回16.

 >>> 表示無符號右移,左邊補(bǔ)0耗啦;
 a |= b表示將a與b做或運(yùn)算再賦值給a君纫,比如:
                                    int a = 5; // 0000 0101
                                    int b = 3; // 0000 0011
                                    a |= b; // 0000 00111

接下來解釋一下這個(gè)方法具體怎么做:
int n = cap - 1   的作用是假如用戶傳入的就是一個(gè)2的n次冪的數(shù)。比如8.這里做減1操作芹彬,就是為了讓結(jié)果是8蓄髓,而不是16.先記住這個(gè)結(jié)論。
然后接下來是一堆的位運(yùn)算舒帮,這部分運(yùn)算就是為了得到一個(gè)最高位與用戶輸入的最高位的一致的2的n次冪-1的數(shù)会喝;
 eg: 假設(shè)用戶輸入的數(shù)為   
                            0001xxxx xxxxxxxx(不弄32位了,大概一下能看懂就行)
        temp = n >>> 1   : 00001xxx xxxxxxxx
        n |= temp        : 00011xxx xxxxxxxx
        temp n >>> 2     : 0000011x xxxxxxxx
        n |= temp        : 0001111x xxxxxxxx
        temp =n >>> 4    : 00000001 111xxxxx
        n |= temp        : 00011111 111xxxxx
        ...
        會得到一個(gè) 000111111 11111111這樣的數(shù)字玩郊。
        為什么最多要到 n |= n >>> 16;因?yàn)閕nt是32位的肢执,經(jīng)過1 2 4 8 16剛好就可以處理最多32位的數(shù)
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
        最后將剛才的結(jié)果+1后,就可以得到一個(gè)
        00100000 00000000 --> 2^13次方的數(shù)译红;
現(xiàn)在在回頭看看 int n = cap - 1预茄,就能知道它的用意了吧。

3侦厚、HashMap的容量為什么是2的n次方耻陕?

其實(shí)這個(gè)與HashMap的put方法有關(guān)。這里有必要先說下HashMap的存儲過程:
  1刨沦、計(jì)算出key的hash值诗宣,將該值進(jìn)一步散列一下(就算使得該值的二進(jìn)制中0101分布得更加均勻)
  2、將上述得到的值與HashMap的容量length-1做&運(yùn)算(因?yàn)槠淙萘渴?的n次方想诅,故length-1的二進(jìn)制一定是類似于 0111 1111 這樣的)召庞,&運(yùn)算的結(jié)果就是該key在table中的索引位置。
這里再補(bǔ)充一下来破,為什么不用取余操作來確定索引位置篮灼?
  --使用的位運(yùn)算,是非常高效的運(yùn)算徘禁。比取余快得多诅诱。且當(dāng)length為2^n時(shí),hash & (length - 1) == hash % length晌坤。
至于是先有這樣的存儲過程的設(shè)計(jì)后決定容量是2的n次方逢艘,還是因?yàn)槿萘渴?的n次方而設(shè)計(jì)出了這種存儲方式。那就不得而知了骤菠。

4它改、關(guān)于HashMap中的hash()方法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
該處為什么要(h = key.hashCode()) ^ (h >>> 16)這么處理?
-- 因?yàn)檎N覀冊谑褂肏ashMap的時(shí)候商乎,map的容量其實(shí)不會太大央拖。正常都是小于2^16=65536的。
   這樣的話鹉戚,會導(dǎo)致一個(gè)問題鲜戒,就是每次key的hash值與map的容量在做&操作的時(shí)候(即上一個(gè)問題里說的存儲過程),
   基本都是hashcode的高16位都用不到抹凳,這樣可能會導(dǎo)致部分不夠均勻遏餐。
   于是,要合理的將高16位也參數(shù)計(jì)算赢底。故^ (h >>> 16)失都。
   這里之所以選擇異或操作,是因?yàn)楫惢虿僮鞯慕Y(jié)果是五五開的幸冻。而&跟|的話粹庞,會讓結(jié)果往1或者0偏。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洽损,一起剝皮案震驚了整個(gè)濱河市庞溜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碑定,老刑警劉巖流码,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異延刘,居然都是意外死亡旅掂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門访娶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來商虐,“玉大人,你說我怎么就攤上這事崖疤∶爻担” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵劫哼,是天一觀的道長叮趴。 經(jīng)常有香客問我,道長权烧,這世上最難降的妖魔是什么眯亦? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任伤溉,我火速辦了婚禮,結(jié)果婚禮上妻率,老公的妹妹穿的比我還像新娘乱顾。我一直安慰自己,他們只是感情好宫静,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布走净。 她就那樣靜靜地躺著,像睡著了一般孤里。 火紅的嫁衣襯著肌膚如雪伏伯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天捌袜,我揣著相機(jī)與錄音说搅,去河邊找鬼。 笑死虏等,一個(gè)胖子當(dāng)著我的面吹牛蜓堕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播博其,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼套才,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慕淡?” 一聲冷哼從身側(cè)響起背伴,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峰髓,沒想到半個(gè)月后傻寂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡携兵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年疾掰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徐紧。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡静檬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出并级,到底是詐尸還是另有隱情拂檩,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布嘲碧,位于F島的核電站稻励,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏愈涩。R本人自食惡果不足惜望抽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一加矛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煤篙,春花似錦斟览、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狸棍。三九已至身害,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間草戈,已是汗流浹背塌鸯。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唐片,地道東北人丙猬。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像费韭,于是被迫代替她去往敵國和親茧球。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348