java集合基礎(chǔ)總結(jié)----HashMap

對java集合相關(guān)的一些基礎(chǔ)總結(jié)定枷,具體每個類可以通過IDE查看源碼,這里會借助一些源碼來說明,但是不進行源碼全面分析

注:源碼基于jdk1.8版本

HashMap
HashMap是平時很常用的,也是各公司面試筆試都會涉及到的
平時使用的時候大概是這樣的

Map myMap = new HashMap();

用的多鸟整,其基本構(gòu)造,我們應(yīng)該了解一下川慌。
下面我們對HashMap結(jié)合源碼進行一個簡單的分析吃嘿,如果不想看分析過程,可以拉到最下面查看總結(jié)梦重。

HashMap類的聲明是這樣的

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

HashMap的大致結(jié)構(gòu)是這樣的

HashMap大致結(jié)構(gòu).png

(手繪的兑燥,粗糙了些,見諒)
大致就是這個樣子
縱向的是一個類似于數(shù)組的結(jié)構(gòu)琴拧,橫向的類似于鏈表
縱向的每一個降瞳,1,2,3,4,5等等,這個有個名稱bucket 一般翻譯為桶

HashMap容量和擴容

  1. 默認創(chuàng)建的HashMap的大小是16蚓胸,就是說上面那個圖在默認情況下挣饥,縱向有16個格子(16個桶)。在源碼中是這樣定義的
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

其中的1<<4指的是二進制的1沛膳,往左邊移動4位扔枫,移過的地方補0,1就變成了二進制的10000锹安,這個就是十進制的16
如果是1被左移的話短荐,可以理解為2^n倚舀,就是2的多少次方

在創(chuàng)建的時候,我們可以給構(gòu)造函數(shù)傳入一個大小忍宋,來指定HashMap的初始大小

Map myMap = new HashMap(30);

我們這里傳入的是30痕貌,是不是就是有30個桶的HashMap呢?
不是。
跟著源碼過去看看糠排,對傳入的容量大小舵稠,有個計算過程:

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;
    }

其中的n>>>1表示將n的二進制數(shù),右移1位入宦,高位補0哺徊,比如 n=5,二進制就是0101, 5>>>1之后是0010
n |= n>>>1 就是 n = n|(n>>>1)
其中的 |乾闰,與運算符唉工,也是二進制層面的,計算規(guī)則就是
1|0 = 1
1|1 = 1
0|0 = 0
依然用n=5舉例汹忠,我們來計算 n |= n>>>1
首先5的二進制數(shù)字是101, 5>>>1=0010
那么101|10是多少呢雹熬,按位與:
101
010
結(jié)果 111
按位與的結(jié)果是 111
好了宽菜,有了這個計算基礎(chǔ),可以自行舉例通過上面的算法計算出的結(jié)果
這個算法的作用就是找到大于等于傳進去的容量數(shù)值的最小的2的冪
什么意思呢
比如你輸入的是30竿报,那么大于等于30的最小的2的冪是2^5,32
所以铅乡,輸入進去的容量,不一定是最終HashMap的容量
然后烈菌,最大容量:

static final int MAXIMUM_CAPACITY = 1 << 30;
  1. 在這類集合類型的類中阵幸,有一個概念,負載因子芽世,HashMap的負載因子默認為0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

意思就是挚赊,16*0.75 = 12,在有12個桶裝了東西之后济瓢,就需要擴容沃粗,這里涉及兩個問題壁榕,第一,擴容的時機,第二督函,擴容的機制

首先,擴容的時機对供,不同的版本逾苫,時機不同,可以從put源碼里看到柬帕,具體代碼不貼了哟忍,有IDE的同學順手就能找到狡门。
在jdk1.8里面,put 調(diào)用的是 putVal魁索,這個方法最后融撞,在將元素添加完成后,有一個操作

        if (++size > threshold)
            resize();

這里的 threshold 不是上面我們計算出來的實際容量粗蔚,是putVal剛開始的resize()方法中乘以負載因子的一個容量
實際意思就是尝偎,每次put執(zhí)行之后,會對當前size增加1鹏控,然后和 容量*負載因子 進行比較致扯,提前進行擴容操作
然后,在jdk1.7里面当辐,擴容是在每次往里put之前執(zhí)行的抖僵,源碼我們下面講解put的時候回分析。
所以缘揪,如果有面試官提問耍群,HashMap的擴容時機,有可能是挖了一個坑找筝,看你對版本變更是否熟悉蹈垢。

然后,擴容的機制袖裕,在resize()中曹抬,主要就是一句

else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold

newCap = oldCap << 1 這個就是計算新的容量,<<1急鳄,左移1位谤民,就是乘以2的意思。
新的容量是舊容量的兩倍疾宏。

上面講的都是桶的擴容张足,比如默認是16個桶,負載因子是0.75灾锯,有12個桶裝了東西之后兢榨,就會觸發(fā)擴容,單個桶里裝了多少顺饮,不計算在內(nèi)吵聪。

HashMap添加數(shù)據(jù)
不確定是從哪個版本開始,HashMap的初始化是在第一次put的時候才進行兼雄,在沒有put數(shù)據(jù)的時候吟逝,沒有進行初始化,以此節(jié)約內(nèi)存赦肋。
簡單分析一下添加數(shù)據(jù)的過程

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

我們putk v之后块攒,實際調(diào)用了putVal(hash(key), key, value, false, true)励稳,第一個參數(shù)就是hash(key),這個是計算key的hash值

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

整個計算過程囱井,沒有興趣的話不用深究驹尼,但從這里至少可以看出,HashMap是可以接受 key為null 的庞呕,這個是HashMap的特性之一新翎。
如果key為null,那么它的hash值就是0住练,如果key不為null地啰,那么通過后邊的算法給計算一個出來。
接下來就是putVal的源碼讲逛,在1.8里亏吝,這個代碼寫的有點復雜,看著頭疼盏混,我們可以回頭去看看1.7里面的put過程蔚鸥,1.8只是進行了一些優(yōu)化,大致過程沒有變许赃。
下面是jdk1.7里面的put的源碼株茶。

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<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;
    }

這個代碼看著就親切多了。图焰。。
部分同學可能不喜歡加注解的分析方式蹦掐,所以這里我就分開一段一段的解析技羔。

        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }

這一段就是判斷整個HashMap是否為空,如果為空卧抗,則初始化

 if (key == null)
            return putForNullKey(value);

HashMap允許key為null藤滥,那么key是null的時候,直接調(diào)用 putForNullKey 處理

private V putForNullKey(V value) {
        for (Entry<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;
    }

從這里可以看出來社裆,key為null的話拙绊,直接存入table[0],如果已經(jīng)有一個key為null的值存在了泳秀,那么替換掉原來的value标沪,將新的value放進去。

for (Entry<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;
            }
        }

這一段是查找是否已經(jīng)有一個相同的key存在了
比較兩個key是否相同的過程嗜傅,可以關(guān)注一下

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 

第一金句,hash值必須相等,hash怎么算出來的吕嘀,可以自己去看看代碼
第二违寞,key值相等贞瞒,或者equal
滿足這兩個條件,就認為是相同的key趁曼,用新的value军浆,替換舊的value。

最后挡闰,對于新的值乒融,使用

 addEntry(hash, key, value, i);

這種方式插入進去,這個方法的源碼

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

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

可以看到尿这,在jdk1.7版本里簇抵,擴容是在添加新數(shù)據(jù)的時候發(fā)生的,原容量x2進行擴容
然后 createEntry就是創(chuàng)建一個新的節(jié)點

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

從源碼可以看出射众,這個桶原來的第一個值碟摆,被挪到后邊去了,看看Entry的結(jié)構(gòu)

Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

第四個參數(shù) e 叨橱,就是next
所以典蜕,如果當前桶已經(jīng)有值了,新增加的值是添加到開頭的罗洗。

對HashMap的基礎(chǔ)總結(jié)大概就這些愉舔,后邊有新的內(nèi)容在添加。


總結(jié)

HashMap 的默認初識容量為 16伙菜,負載因子為 0.75 轩缤,擴容機制是 當前容量x2
擴容時機,不同版本不一樣贩绕,1.7版本是添加新內(nèi)容是檢測擴容火的,1.8版本是添加完成后檢測
那么,我們在使用的時候淑倾,為了避免頻繁擴容帶來的消耗馏鹤,對HashMap的初始容量就需要結(jié)合實際情況計算一下,比如娇哆,當前應(yīng)用湃累,放入HashMap的成員最多300個,那么碍讨,300/0.75=400,那我們對于HashMap的初始大小治力,至少需要400.
非線程安全,整個源碼里沒有使用同步機制
允許key為null勃黍,允許value為null琴许。
所以,這里有個問題溉躲,如果我們使用get獲取value值時榜田,如果得到的時value益兄,無法確定這個null是因為HashMap里沒有這個key返回的null還是value值就是null,這個時候需要用containsKey先判斷一次箭券。這樣就比較麻煩了净捅,一般我們不往里邊存null。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辩块,一起剝皮案震驚了整個濱河市蛔六,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌废亭,老刑警劉巖国章,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豆村,居然都是意外死亡液兽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門掌动,熙熙樓的掌柜王于貴愁眉苦臉地迎上來四啰,“玉大人,你說我怎么就攤上這事粗恢「躺梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵眷射,是天一觀的道長匙赞。 經(jīng)常有香客問我,道長妖碉,這世上最難降的妖魔是什么罚屋? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮嗅绸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撕彤。我一直安慰自己鱼鸠,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布羹铅。 她就那樣靜靜地躺著蚀狰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪职员。 梳的紋絲不亂的頭發(fā)上麻蹋,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音焊切,去河邊找鬼扮授。 笑死芳室,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的刹勃。 我是一名探鬼主播堪侯,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼荔仁!你這毒婦竟也來了伍宦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乏梁,失蹤者是張志新(化名)和其女友劉穎次洼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遇骑,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡卖毁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了质蕉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片势篡。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖模暗,靈堂內(nèi)的尸體忽然破棺而出禁悠,到底是詐尸還是另有隱情,我是刑警寧澤兑宇,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布碍侦,位于F島的核電站,受9級特大地震影響隶糕,放射性物質(zhì)發(fā)生泄漏瓷产。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一枚驻、第九天 我趴在偏房一處隱蔽的房頂上張望濒旦。 院中可真熱鬧,春花似錦再登、人聲如沸尔邓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梯嗽。三九已至,卻和暖如春沽损,著一層夾襖步出監(jiān)牢的瞬間灯节,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炎疆,地道東北人卡骂。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像磷雇,于是被迫代替她去往敵國和親偿警。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

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

  • HashMap 源碼分析 前幾篇分析了 ArrayList 唯笙, LinkedList 螟蒸,Vector ,Stack...
    醒著的碼者閱讀 2,849評論 4 44
  • HashMap 是 Java 面試必考的知識點崩掘,面試官從這個小知識點就可以了解我們對 Java 基礎(chǔ)的掌握程度七嫌。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,669評論 9 107
  • 1.概述 HashMap是日常java開發(fā)中常用的類之一诵原,是java設(shè)計中非常經(jīng)典的一個類,它巧妙的設(shè)計思想與實現(xiàn)...
    Garwer閱讀 2,235評論 1 28
  • 按照從構(gòu)造方法->常用API(增挽放、刪绍赛、改、查)的順序來閱讀源碼辑畦,并做簡要分析吗蚌。 一. 概要 概括的說HashMap...
    stoneyang94閱讀 367評論 0 1
  • 故事里面的女孩子現(xiàn)在已經(jīng)為人妻,為人母纯出,可是我記憶里她們依舊是那些女孩兒蚯妇。在開始我們的故事以前,我得先講一講每一個...
    正版艾小嫻閱讀 230評論 0 0