圖解HashMap

什么是HashMap,文章內(nèi)HashMap源碼主要來自Android 7.0

HashMap是開發(fā)中常用的一個(gè)類巷屿,那么他究竟是什么呢导俘?

HashMap是一個(gè)存儲key-value的集合槐雾,底層實(shí)現(xiàn)的是數(shù)組巾遭,所以可以看作HashMap是對數(shù)組的一種封裝。

構(gòu)造方法

HashMap構(gòu)造函數(shù).png

HashMap構(gòu)造函數(shù).png

不管調(diào)用的是哪一個(gè)方法平道, 最終都會回調(diào)兩個(gè)參數(shù)的這個(gè)構(gòu)造函數(shù)睹欲,第一個(gè)參數(shù)是容量,第二個(gè)參數(shù)是閾值(用于擴(kuò)容的時(shí)候計(jì)算容量)

先看看HashMap主要的成員變量

  /**
     * HashMap默認(rèn)容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 4;

    /**
     * HashMap最大可存儲的容量值  1<<30
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 加載因子(閾值)如果put進(jìn)來的元素?cái)?shù)量>=總數(shù)量*0.75的時(shí)候一屋, 就會進(jìn)行擴(kuò)容了
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * EMPTY_TABLE 看了一下窘疮,好像沒啥用。冀墨。考余。
     */
    static final HashMapEntry<?,?>[] EMPTY_TABLE = {};
    transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;

    /**
     * 這個(gè)size表示容量值,put了幾次轧苫,這個(gè)size就是幾楚堤,所以我們方法中用的size() 就是返回的這個(gè)值
     */
    transient int size;

因?yàn)镠ashMap常用的就是get和put,所以主要分析一下這兩個(gè)方法含懊,在講這個(gè)之前身冬,先看一下HashMapEntry這個(gè)類吧

HashMapEntry

HashMapEntry繼承自Map.Entry

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

HashMapEntry的結(jié)構(gòu)是鏈表(在api25之前是鏈表,在api26開始引入了紅黑樹, 當(dāng)節(jié)點(diǎn)>8個(gè)的時(shí)候會轉(zhuǎn)為紅黑樹, 節(jié)點(diǎn)<6個(gè)的時(shí)候又會轉(zhuǎn)回為鏈表, 紅黑樹跳這里HashMap在Api26后的應(yīng)用---紅黑樹篇),所以存儲數(shù)據(jù)的時(shí)候是這樣的

存儲結(jié)構(gòu).png

關(guān)于鏈表可參考其他文章

現(xiàn)在來講一講HashMap的put和get

put

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

整個(gè)put的方法并不長岔乔,首次進(jìn)來時(shí)會判斷table是不是EMPTY_TABLE酥筝,就是上面那兩數(shù)組,然后會執(zhí)行inflatetable方法雏门,這個(gè)方法就不看了嘿歌。。茁影。只有第一次put時(shí)候才會進(jìn)入宙帝,因?yàn)橹挥心莻€(gè)時(shí)候table==EMPTY_TABLE,在inflatetable里募闲,table就會被重新賦值
接下來看第二個(gè)判斷 key==null
看看這個(gè)方法putForNullKey()

 private V putForNullKey(V value) {
        for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

如果已經(jīng)有了一個(gè) key為null的元素步脓,那么就會替換他的value值,所以HashMap只能由一個(gè)空key。
sun.misc.Hashing.singleWordWangJenkinsHash(key);這個(gè)方法就是根據(jù)key計(jì)算hash值靴患,然后通過indexFor方法算出key在table中的下標(biāo)仍侥。由于數(shù)組的存儲方式大概是這樣的

image.png

但是由于下標(biāo)是根據(jù)key的hash和數(shù)組長度計(jì)算來的,所以有可能下標(biāo)會一樣鸳君,這個(gè)時(shí)候HashMapEntry這個(gè)鏈表的用處就體現(xiàn)出來了农渊,如果下標(biāo)一樣的時(shí)候,那么就會比對HashMapEntry的key值是否一致或颊,如果一致砸紊,就替換原key-value,如果沒有與新添加的key一致的值饭宾,就會在HashMapEntry中新加一個(gè)節(jié)點(diǎn)批糟,所以現(xiàn)在的存儲方式變成了這樣

hashmap存儲方式.png

如果是替換就value格了,會直接吧舊的value返回回去看铆,如果不是的話就會走addEntry方法, 這個(gè)方法有三個(gè)作用

  • 擴(kuò)容
  • 拷貝數(shù)據(jù)
  • 插入新數(shù)據(jù)
    跟進(jìn)一下addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

首先判斷的是size是否大于閾值(總?cè)萘?0.75)盛末,并且table[bucketIndex]弹惦!=null, 所以只有兩個(gè)條件成立的時(shí)候才會進(jìn)行擴(kuò)容

resize()

void resize(int newCapacity) {
        HashMapEntry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        HashMapEntry[] newTable = new HashMapEntry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

newCapacity的大小等于就數(shù)組長度*2, 所以下方構(gòu)建的newTable的長度就是原數(shù)組的長度兩倍悄但,到這里棠隐,就進(jìn)行擴(kuò)容完畢了,但是新數(shù)組是有了檐嚣,但是沒數(shù)據(jù)爸蟆!不急嚎京,看transfer方法

transfer()

void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (HashMapEntry<K,V> e : table) {
            while(null != e) {
                HashMapEntry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

看到了吧嗡贺,或進(jìn)行一個(gè)雙層循環(huán),先循環(huán)數(shù)組鞍帝,然后在循環(huán)里面節(jié)點(diǎn)诫睬,直到next==null的時(shí)候,會跳出當(dāng)前循環(huán)帕涌,進(jìn)行下一次循環(huán)摄凡,直到循環(huán)完畢,也就是新數(shù)據(jù)賦值完畢
再回到resize方法蚓曼,再看下面的代碼亲澡,把新數(shù)組newTable又給了table,threshold又得到了擴(kuò)容后新的閾值纫版,到這一步谷扣,擴(kuò)容和拷貝數(shù)據(jù)就已經(jīng)完成了。
再回看addEntry方法,又會更具新數(shù)組的大小和key的hash值重新計(jì)算下標(biāo)会涎,傳遞給createEntry(hash, key, value, bucketIndex)方法中裹匙,

 void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
        size++;
    }

到此,hashmap的put就結(jié)束了末秃,回頭看看概页。。练慕。其實(shí)還算蠻簡單的哈


毛骨悚然.png

那么get方法呢惰匙?

get

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

get方法最終會調(diào)用這個(gè)getEntry方法,看看里面的方法是不是很眼熟铃将,計(jì)算hash项鬼,比對key。



對劲阎!就是這么簡單绘盟,同樣也是根據(jù)hash和數(shù)組長度獲取下標(biāo),然后就是這么一個(gè)循環(huán)悯仙,只要hash值一樣并且key有一樣的就會返回這個(gè)元素龄毡,否則就是返回null

總結(jié)一下:
put添加元素的操作為:

計(jì)算key的hash ==> 根據(jù)hash和數(shù)組長度計(jì)算對應(yīng)的數(shù)組下標(biāo) ==> 如果當(dāng)前下標(biāo)內(nèi)容為null,就直接添加锡垄,否則的話會進(jìn)入一個(gè)循環(huán)沦零,在這個(gè)循環(huán)中去尋找鏈表內(nèi)有沒有當(dāng)前key值,有的話替換原value货岭,沒有的話插入到最后一個(gè)節(jié)點(diǎn)

put步驟.png

get獲取元素

計(jì)算key的hash ==> 根據(jù)hash和數(shù)組長度計(jì)算對應(yīng)的數(shù)組下標(biāo) ==> 如果當(dāng)前下標(biāo)元素不為null路操,進(jìn)入循環(huán),在這個(gè)循環(huán)中去尋找鏈表內(nèi)有沒有當(dāng)前key值千贯,有的話返回屯仗,沒有的話就返回null
get就不畫了啊 自行體會


話說你們畫圖都用啥啊。丈牢。祭钉。 我這大晚上的用截圖工具扣扣畫畫好累,win10自帶的畫圖工具感覺用不來

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末己沛,一起剝皮案震驚了整個(gè)濱河市慌核,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌申尼,老刑警劉巖垮卓,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異师幕,居然都是意外死亡粟按,警方通過查閱死者的電腦和手機(jī)诬滩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灭将,“玉大人疼鸟,你說我怎么就攤上這事∶硎铮” “怎么了空镜?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捌朴。 經(jīng)常有香客問我吴攒,道長,這世上最難降的妖魔是什么砂蔽? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任洼怔,我火速辦了婚禮,結(jié)果婚禮上左驾,老公的妹妹穿的比我還像新娘镣隶。我一直安慰自己,他們只是感情好什荣,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布矾缓。 她就那樣靜靜地躺著怀酷,像睡著了一般稻爬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜕依,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天桅锄,我揣著相機(jī)與錄音,去河邊找鬼样眠。 笑死友瘤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檐束。 我是一名探鬼主播辫秧,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼被丧!你這毒婦竟也來了盟戏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤甥桂,失蹤者是張志新(化名)和其女友劉穎柿究,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黄选,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝇摸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片貌夕。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡律歼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啡专,到底是詐尸還是另有隱情苗膝,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布植旧,位于F島的核電站辱揭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏病附。R本人自食惡果不足惜问窃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望完沪。 院中可真熱鬧域庇,春花似錦、人聲如沸覆积。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宽档。三九已至尉姨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吗冤,已是汗流浹背又厉。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椎瘟,地道東北人覆致。 一個(gè)月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像肺蔚,于是被迫代替她去往敵國和親煌妈。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

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

  • 1. 前言 本文的源碼是基于JDK1.7宣羊,JDK1.8中HashMap的實(shí)現(xiàn)璧诵,引入了紅黑樹,在后面的文章會寫到段只。后...
    唐江旭閱讀 39,768評論 16 175
  • HashMap 是 Java 面試必考的知識點(diǎn)腮猖,面試官從這個(gè)小知識點(diǎn)就可以了解我們對 Java 基礎(chǔ)的掌握程度。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,669評論 9 107
  • 在看《紅高粱家族》與它的電視劇
    自動控制原理要加油閱讀 215評論 0 1
  • 這里是烏鎮(zhèn)赞枕,一個(gè)夢里的水鄉(xiāng)澈缺,一個(gè)來過就不曾離開的地方坪创。 我在這里,等你姐赡。 你若能來莱预,我必生死相依。
    夏夏大小姐閱讀 122評論 0 0
  • Hello World
    九袋大弟子閱讀 180評論 0 0