HashMap梳理

概述

HashMap是基于哈希表的 Map 接口的實(shí)現(xiàn)。數(shù)據(jù)以鍵值對(duì)的形式存儲(chǔ)救欧,和HashTab的差別在于HashMap可以以null作為鍵值,但是HashMap是線程不安全的。如果要實(shí)現(xiàn)線程同步可以使用:

  • Map map = Collections.synchronizedMap(new HashMap());
  • ConcurrentHashMap

HashMap的數(shù)據(jù)結(jié)構(gòu)

HashMap是通過(guò)數(shù)組和鏈表(散列鏈表)來(lái)實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)的难衰。之所以HashMap查詢速度很快,是因?yàn)樗峭ㄟ^(guò)散列碼來(lái)決定存儲(chǔ)位置逗栽。通過(guò)獲取key的hashcode和數(shù)組的長(zhǎng)度的&值來(lái)確定存儲(chǔ)的位置盖袭,如果有相同的key的hashcode,那么就是所謂的hash沖突,就添加到對(duì)應(yīng)的鏈表結(jié)構(gòu)的數(shù)據(jù)中苍凛。

image.png

紫色代表數(shù)組代表哈希表趣席,也稱為哈希數(shù)組,綠色代表鏈表醇蝴。數(shù)組存儲(chǔ)具有相同key值hashcode的鏈表的表頭宣肚。數(shù)組的元素和鏈表的節(jié)點(diǎn)都是Entry對(duì)象。

HashMap源碼分析(Android中的源碼)

  • HashMapEntry對(duì)象:
/** HashMapEntry是單向鏈表悠栓。    
    * 它是 “HashMap鏈?zhǔn)酱鎯?chǔ)法”對(duì)應(yīng)的鏈表霉涨。    
    * 它實(shí)現(xiàn)了Map.Entry 接口,即實(shí)現(xiàn)getKey(), getValue(), setValue(V value), equals(Object o), hashCode()這些函數(shù)  
    **/
static class HashMapEntry<K, V> implements Entry<K, V> {
        final K key;
        V value;
        final int hash;
        HashMapEntry<K, V> next;

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

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

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

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

        @Override public final int hashCode() {
            return (key == null ? 0 : key.hashCode()) ^
                    (value == null ? 0 : value.hashCode());
        }

        @Override public final String toString() {
            return key + "=" + value;
        }
    }

HashMapEntry就是一個(gè)單向鏈表惭适,每個(gè)節(jié)點(diǎn)包含了key笙瑟、value、hash值癞志、和下一個(gè)節(jié)點(diǎn)往枷。

  • 重要屬性:
private static final int MINIMUM_CAPACITY = 4;//最小的容量,也是默認(rèn)的初始容量
static final float DEFAULT_LOAD_FACTOR = .75F;//默認(rèn)的加載因子
transient HashMapEntry<K, V>[] table;//哈希表
transient int modCount;//被修改的次數(shù)
transient int size;//存放元素的個(gè)數(shù)
private transient int threshold;//閾值凄杯,當(dāng)前的元素個(gè)數(shù)查過(guò)閾值進(jìn)行擴(kuò)容错洁,閾值 = 加載因子*總?cè)萘?

加載因子在Android 的源碼中被閹割了,固定為0.75戒突,這是和在jdk中不同的地方

public HashMap(int capacity, float loadFactor) {
        this(capacity);

        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new IllegalArgumentException("Load factor: " + loadFactor);
        }

        /* 加載因子固定為3/4
         * Note that this implementation ignores loadFactor; it always uses
         * a load factor of 3/4. This simplifies the code and generally
         * improves performance.
         */
    }

加載因子表示HashMap填充的程度屯碴,加載因子越大,HashMap填充的越滿才進(jìn)行擴(kuò)容膊存,空間利用率越高导而,造成的問(wèn)題是存放的數(shù)據(jù)越多,hash沖突的可能性越大隔崎,查找的效率越低今艺。反之亦反。這是一個(gè)空間和效率的之間的取舍爵卒。一般用默認(rèn)的就行了洼滚。

  • put方法:
public V put(K key, V value) {
        if (key == null) {
            //存放null的key值,則將該鍵值對(duì)添加到table[0]中技潘。
            return putValueForNullKey(value);
        }
        //對(duì)key的hashcode值進(jìn)行再次計(jì)算得到hash遥巴。
        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        //通過(guò)hash值和數(shù)組長(zhǎng)度來(lái)確定數(shù)組的下標(biāo),這里的值不是隨便取的
        int index = hash & (tab.length - 1);
        //遍歷對(duì)應(yīng)的數(shù)組下標(biāo)的鏈表享幽,如果存在相同的key值就替換原來(lái)的value
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        //否則加載列表的頭部(也就是數(shù)組對(duì)應(yīng)的位置)
        //在添加新的數(shù)據(jù)之前檢查是否需要擴(kuò)容铲掐。如果數(shù)據(jù)的大小超過(guò)閾值,數(shù)組的容量就擴(kuò)大到原來(lái)的2倍
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        //把數(shù)據(jù)添加到表頭
        addNewEntry(key, value, hash, index);
        return null;
    }

hash & (tab.length - 1)的算法來(lái)取到數(shù)組的下標(biāo)值值桩,這個(gè)方式即可實(shí)現(xiàn)均勻的散列摆霉,還可以使數(shù)組不越界。那為什么擴(kuò)容需要2的次冪呢,因?yàn)閔ash & (tab.length - 1)中携栋,如果括號(hào)中的結(jié)果數(shù)奇數(shù)的話最后一位為1搭盾,hash&xx最后的接口有可能是奇數(shù)也有可能是偶數(shù);如果括號(hào)中的值是偶數(shù)的話婉支,那最后的結(jié)果也只能是偶數(shù)鸯隅,那么數(shù)組存放的值只能存放在偶數(shù)下標(biāo)中,浪費(fèi)了一般的資源向挖,如果是2的倍數(shù)減一蝌以,剛好能夠控制能過(guò)取到所有的下標(biāo)值。因此何之,length取2的整數(shù)次冪跟畅,是為了使不同hash值發(fā)生碰撞的概率較小,這樣就能使元素在哈希表中均勻地散列溶推。
擴(kuò)容:

private HashMapEntry<K, V>[] doubleCapacity() {
        HashMapEntry<K, V>[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            return oldTable;
        }
        int newCapacity = oldCapacity * 2;
        HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
        if (size == 0) {
            return newTable;
        }

        for (int j = 0; j < oldCapacity; j++) {
            /*
             * Rehash the bucket using the minimum number of field writes.
             * This is the most subtle and delicate code in the class.
             */
            HashMapEntry<K, V> e = oldTable[j];
            if (e == null) {
                continue;
            }
            int highBit = e.hash & oldCapacity;
            HashMapEntry<K, V> broken = null;
            newTable[j | highBit] = e;
            for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
                int nextHighBit = n.hash & oldCapacity;
                if (nextHighBit != highBit) {
                    if (broken == null)
                        newTable[j | nextHighBit] = n;
                    else
                        broken.next = n;
                    broken = e;
                    highBit = nextHighBit;
                }
            }
            if (broken != null)
                broken.next = null;
        }
        return newTable;
    }

新建了一個(gè)數(shù)組徊件,是原來(lái)數(shù)組容量的兩倍,然后重新計(jì)算hashcode在數(shù)組中的位置蒜危,并且存放在數(shù)組中虱痕。
那為什么擴(kuò)容呢?隨著HashMap存放的數(shù)據(jù)越來(lái)越多舰褪,hash沖入產(chǎn)生的概率就越來(lái)越大,造成查找效率越來(lái)越低疏橄,所以進(jìn)行擴(kuò)容占拍,但是擴(kuò)容需要把所有的元素遍歷重新賦值,還要新建一個(gè)數(shù)組捎迫,這是HashMap很消耗資源的地方晃酒。
在達(dá)到閾值的時(shí)候開(kāi)始擴(kuò)容,如:現(xiàn)在的容量大小為8,8*0.75 = 6窄绒,當(dāng)HashMap中的元素個(gè)數(shù)達(dá)到6的時(shí)候就開(kāi)始擴(kuò)容贝次。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彰导,隨后出現(xiàn)的幾起案子蛔翅,更是在濱河造成了極大的恐慌,老刑警劉巖位谋,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件山析,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掏父,警方通過(guò)查閱死者的電腦和手機(jī)笋轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人爵政,你說(shuō)我怎么就攤上這事仅讽。” “怎么了钾挟?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵洁灵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我等龙,道長(zhǎng)处渣,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任蛛砰,我火速辦了婚禮罐栈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泥畅。我一直安慰自己荠诬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布位仁。 她就那樣靜靜地躺著柑贞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪聂抢。 梳的紋絲不亂的頭發(fā)上钧嘶,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音琳疏,去河邊找鬼有决。 笑死,一個(gè)胖子當(dāng)著我的面吹牛空盼,可吹牛的內(nèi)容都是我干的书幕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼揽趾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼台汇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起篱瞎,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤苟呐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后俐筋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掠抬,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年校哎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了两波。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞳步。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腰奋,靈堂內(nèi)的尸體忽然破棺而出单起,到底是詐尸還是另有隱情,我是刑警寧澤劣坊,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布嘀倒,位于F島的核電站,受9級(jí)特大地震影響局冰,放射性物質(zhì)發(fā)生泄漏测蘑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一康二、第九天 我趴在偏房一處隱蔽的房頂上張望碳胳。 院中可真熱鬧,春花似錦沫勿、人聲如沸挨约。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诫惭。三九已至,卻和暖如春蔓挖,著一層夾襖步出監(jiān)牢的瞬間夕土,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工瘟判, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怨绣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓荒适,卻偏偏與公主長(zhǎng)得像梨熙,于是被迫代替她去往敵國(guó)和親开镣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刀诬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 一陕壹、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,261評(píng)論 0 16
  • 實(shí)際上,HashSet 和 HashMap 之間有很多相似之處树埠,對(duì)于 HashSet 而言糠馆,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,513評(píng)論 1 37
  • 整個(gè)三月份匆匆忙忙的過(guò)去了
    橙鉆萌主閱讀 133評(píng)論 0 0
  • 從上周六下午開(kāi)始頭疼,太陽(yáng)穴兩邊也就是腦仁兒處總是伴隨著心跳“突怎憋、突又碌、突”的疼九昧。我們?cè)谡;顒?dòng)的時(shí)候一般感覺(jué)不到自...
    ZQ鄭閱讀 556評(píng)論 1 1
  • 看完《春宴》毕匀,心有所感铸鹰,想說(shuō)的,也許皂岔,差不多就只有這些了吧蹋笼。沒(méi)有劇情,人物來(lái)訴說(shuō)躁垛,也沒(méi)有太多的講究剖毯,大概就是這樣吧...
    小二不2閱讀 279評(píng)論 2 3