HashMap原理

HashMap原理

HashMap也是我們使用非常多的Collection麦萤,它是基于哈希表的 Map 接口的實(shí)現(xiàn),以key-value的形式存在本涕。在HashMap中烹笔,key-value總是會(huì)當(dāng)做一個(gè)整體來(lái)處理,系統(tǒng)會(huì)根據(jù)hash算法來(lái)來(lái)計(jì)算key-value的存儲(chǔ)位置澄耍,我們總是可以通過(guò)key快速地存噪珊、取value。下面就來(lái)分析HashMap的存取齐莲。

一痢站、定義

HashMap實(shí)現(xiàn)了Map接口,繼承AbstractMap选酗。其中Map接口定義了鍵映射到值的規(guī)則瑟押,而AbstractMap類(lèi)提供 Map 接口的骨干實(shí)現(xiàn),以最大限度地減少實(shí)現(xiàn)此接口所需的工作星掰,其實(shí)AbstractMap類(lèi)已經(jīng)實(shí)現(xiàn)了Map,這里標(biāo)注Map LZ覺(jué)得應(yīng)該是更加清晰吧嫩舟!

public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable

二氢烘、構(gòu)造函數(shù)

HashMap提供了三個(gè)構(gòu)造函數(shù):
HashMap():
??構(gòu)造一個(gè)具有默認(rèn)初始容量 (16) 和默認(rèn)加載因子 (0.75) 的HashMap。
HashMap(int initialCapacity):
??構(gòu)造一個(gè)帶指定初始容量和默認(rèn)加載因子 (0.75) 的空 HashMap家厌。
HashMap(int initialCapacity, float loadFactor):
??構(gòu)造一個(gè)帶指定初始容量和加載因子的空 HashMap播玖。

在這里提到了兩個(gè)參數(shù):
?? 初始容量,加載因子饭于。這兩個(gè)參數(shù)是影響HashMap性能的重要參數(shù)蜀踏,其中容量表示哈希表中桶的數(shù)量,初始容量是創(chuàng)建哈希表時(shí)的容量掰吕,加載因子是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿(mǎn)的一種尺度果覆,它衡量的是一個(gè)散列表的空間的使用程度,負(fù)載因子越大表示散列表的裝填程度越高殖熟,反之愈小局待。對(duì)于使用鏈表法的散列表來(lái)說(shuō),查找一個(gè)元素的平均時(shí)間是O(1+a),因此如果負(fù)載因子越大钳榨,對(duì)空間的利用更充分舰罚,然而后果是查找效率的降低;如果負(fù)載因子太小薛耻,那么散列表的數(shù)據(jù)將過(guò)于稀疏营罢,對(duì)空間造成嚴(yán)重浪費(fèi)。系統(tǒng)默認(rèn)負(fù)載因子為0.75饼齿,一般情況下我們是無(wú)需修改的饲漾。

HashMap是一種支持快速存取的數(shù)據(jù)結(jié)構(gòu),要了解它的性能必須要了解它的數(shù)據(jù)結(jié)構(gòu)候醒。

三能颁、數(shù)據(jù)結(jié)構(gòu)

我們知道在Java中最常用的兩種結(jié)構(gòu)是數(shù)組和模擬指針(引用),幾乎所有的數(shù)據(jù)結(jié)構(gòu)都可以利用這兩種來(lái)組合實(shí)現(xiàn)倒淫,HashMap也是如此伙菊。實(shí)際上HashMap是一個(gè)“鏈表散列”,如下是它數(shù)據(jù)結(jié)構(gòu):

152128347367.png

從上圖我們可以看出HashMap底層實(shí)現(xiàn)還是數(shù)組敌土,只是數(shù)組的每一項(xiàng)都是一條鏈镜硕。其中參數(shù)initialCapacity就代表了該數(shù)組的長(zhǎng)度。下面為HashMap構(gòu)造函數(shù)的源碼:

public HashMap(int initialCapacity, float loadFactor) {
    //初始容量不能<0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: "+ initialCapacity);
    //初始容量不能 > 最大容量值返干,HashMap的最大容量值為2^30
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //負(fù)載因子不能 < 0
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: "+ loadFactor);

    // 計(jì)算出大于 initialCapacity 的最小的 2 的 n 次方值兴枯。
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    
    this.loadFactor = loadFactor;
    //設(shè)置HashMap的容量極限,當(dāng)HashMap的容量達(dá)到該極限時(shí)就會(huì)進(jìn)行擴(kuò)容操作
    threshold = (int) (capacity * loadFactor);
    //初始化table數(shù)組
    table = new Entry[capacity];
    init();
}
  從源碼中可以看出矩欠,每次新建一個(gè)HashMap時(shí)财剖,都會(huì)初始化一個(gè)table數(shù)組。table數(shù)組的元素為Entry節(jié)點(diǎn)癌淮。
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
    .......
}

其中Entry為HashMap的內(nèi)部類(lèi)躺坟,它包含了鍵key、值value乳蓄、下一個(gè)節(jié)點(diǎn)next咪橙,以及hash值,這是非常重要的虚倒,正是由于Entry才構(gòu)成了table數(shù)組的項(xiàng)為鏈表美侦。

上面簡(jiǎn)單分析了HashMap的數(shù)據(jù)結(jié)構(gòu),下面將探討HashMap是如何實(shí)現(xiàn)快速存取的魂奥。

四菠剩、存儲(chǔ)實(shí)現(xiàn):##

首先我們先看源碼

public V put(K key, V value) {  
//當(dāng)key為null,調(diào)用putForNullKey方法捧弃,保存null與table第一個(gè)位置中赠叼,這是HashMap允許為null的原因    
    if (key == null)    
        return putForNullKey(value);    
    //計(jì)算key的hash值  
    int hash = hash(key.hashCode());                    ------(1)  
    //計(jì)算key hash 值在 table 數(shù)組中的位置  
    int i = indexFor(hash, table.length);               ------(2)  
    //從i出開(kāi)始迭代 e,找到 key 保存的位置  
    for (Entry<K, V> e = table[i]; e != null; e =   e.next) {  
        Object k;  
        //判斷該條鏈上是否有hash值相同的(key相同)  
        //若存在相同擦囊,則直接覆蓋value,返回舊value  
        if (e.hash == hash && ((k = e.key) == key   key.equals(k))) {  
            V oldValue = e.value;    //舊值 = 新值  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;     //返回舊值  
        }  
    }  
    //修改次數(shù)增加1  
    modCount++;  
    //將key嘴办、value添加至i位置處  
    addEntry(hash, key, value, i);  
    return null;  
}  

通過(guò)源碼我們可以清晰看到HashMap保存數(shù)據(jù)的過(guò)程為:首先判斷key是否為null瞬场,若為null,則直接調(diào)用putForNullKey方法涧郊。若不為空則先計(jì)算key的hash值贯被,然后根據(jù)hash值搜索在table數(shù)組中的索引位置,如果table數(shù)組在該位置處有元素妆艘,則通過(guò)比較是否存在相同的key彤灶,若存在則覆蓋原來(lái)key的value,否則將該元素保存在鏈頭(最先保存的元素放在鏈尾)批旺。若table在該處沒(méi)有元素幌陕,則直接保存。這個(gè)過(guò)程看似比較簡(jiǎn)單汽煮,其實(shí)深有內(nèi)幕搏熄。有如下幾點(diǎn):

1、 先看迭代處暇赤。此處迭代原因就是為了防止存在相同的key值心例,若發(fā)現(xiàn)兩個(gè)hash值(key)相同時(shí),HashMap的處理方式是用新value替換舊value鞋囊,這里并沒(méi)有處理key止后,這就解釋了HashMap中沒(méi)有兩個(gè)相同的key。

2溜腐、 在看(1)译株、(2)處。這里是HashMap的精華所在挺益。首先是hash方法古戴,該方法為一個(gè)純粹的數(shù)學(xué)計(jì)算,就是計(jì)算h的hash值矩肩。

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

我們知道對(duì)于HashMap的table而言,數(shù)據(jù)分布需要均勻(最好每項(xiàng)都只有一個(gè)元素肃续,這樣就可以直接找到)黍檩,不能太緊也不能太松,太緊會(huì)導(dǎo)致查詢(xún)速度慢始锚,太松則浪費(fèi)空間刽酱。計(jì)算hash值后,怎么才能保證table元素分布均與呢瞧捌?我們會(huì)想到取模棵里,但是由于取模的消耗較大润文,HashMap是這樣處理的:調(diào)用indexFor方法。

static int indexFor(int h, int length) {
    return h & (length-1);
}

HashMap的底層數(shù)組長(zhǎng)度總是2的n次方殿怜,在構(gòu)造函數(shù)中存在:capacity <<= 1;這樣做總是能夠保證HashMap的底層數(shù)組長(zhǎng)度為2的n次方典蝌。當(dāng)length為2的n次方時(shí),h&(length - 1)就相當(dāng)于對(duì)length取模头谜,而且速度比直接取目ハ疲快得多,這是HashMap在速度上的一個(gè)優(yōu)化柱告。至于為什么是2的n次方下面解釋截驮。

我們回到indexFor方法,該方法僅有一條語(yǔ)句:h&(length - 1)际度,這句話除了上面的取模運(yùn)算外還有一個(gè)非常重要的責(zé)任:均勻分布table數(shù)據(jù)和充分利用空間葵袭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棘钞,一起剝皮案震驚了整個(gè)濱河市欠窒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勃黍,老刑警劉巖块请,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜氏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡墩新,警方通過(guò)查閱死者的電腦和手機(jī)贸弥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)海渊,“玉大人绵疲,你說(shuō)我怎么就攤上這事〕家桑” “怎么了盔憨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)讯沈。 經(jīng)常有香客問(wèn)我郁岩,道長(zhǎng),這世上最難降的妖魔是什么缺狠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任问慎,我火速辦了婚禮,結(jié)果婚禮上挤茄,老公的妹妹穿的比我還像新娘如叼。我一直安慰自己,他們只是感情好穷劈,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布笼恰。 她就那樣靜靜地躺著踊沸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪社证。 梳的紋絲不亂的頭發(fā)上逼龟,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音猴仑,去河邊找鬼审轮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辽俗,可吹牛的內(nèi)容都是我干的疾渣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼崖飘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼榴捡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起朱浴,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吊圾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后翰蠢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體项乒,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年梁沧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了檀何。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廷支,死狀恐怖频鉴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恋拍,我是刑警寧澤垛孔,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站施敢,受9級(jí)特大地震影響周荐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜僵娃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一羡藐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悯许,春花似錦、人聲如沸辉阶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至垃僚,卻和暖如春集绰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谆棺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工栽燕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人改淑。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓碍岔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朵夏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔼啦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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