Java1.8 hashmap 源碼閱讀1

建議先看下文章中提供的源碼惹谐,然后再看解釋可以加深理解齐板。

內(nèi)部靜態(tài)變量

DEFAULT_INITIAL_CAPACITY

默認(rèn)初始化容量

DEFAULT_LOAD_FACTOR

默認(rèn)負(fù)載因子

TREEIFY_THRESHOLD

二叉樹閾值

UNTREEIFY_THRESHOLD

取消二叉樹閾值

MIN_TREEIFY_CAPACITY

二叉樹化所需要的最小容量

內(nèi)部類张吉,Node<K, V>

好像內(nèi)部存儲(chǔ)的hash沒(méi)用到霞势。

hashCode

計(jì)算hash值晕城,

public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
}


靜態(tài)方法

static final inti hash(Object key)

計(jì)算key的hash值泞坦。

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

static Class<?> comparableClassFor(Object x)

TODO

在二叉樹中使用到

static int compareComparables(Class<?> kc, Object k, Object x)

TODO

也是在二叉樹中用到

static final int tableSizeFor(int cap)

傳入容量cap,返回比傳入的容量cap大的最小二次冪砖顷。

變量

table

最終存儲(chǔ)數(shù)據(jù)的地方

entrySet

TODO

一個(gè)緩存用的set

size

容量大小當(dāng)前

modCount

TODO

操作數(shù)贰锁,貌似是線程安全用的

threshold

resize的閾值

loadFactor

負(fù)載因子

公有方法

構(gòu)造方法

public HashMap(int initialCapacity, float loadFactor)
public HashMap(int initialCapacity)
public HashMap()
public HashMap(Map<? extends K, ? extends V> m)

pubMapEntries(Map<? extends K, ? extends V> m, boolean evict)

一次性放入多組鍵值對(duì),也可以叫做鍵值對(duì)的復(fù)制滤蝠。

  • 如果table沒(méi)有初始化則進(jìn)行初始化豌熄,根據(jù)傳入的m計(jì)算最大容量(根據(jù)負(fù)載因子)
  • 如果table初始化了,則判斷是否會(huì)大于閾值物咳,大于閾值則resize
  • for遍歷m锣险,完成數(shù)據(jù)的復(fù)制

注意:putVal函數(shù)

public int size()

返回map的容量

public boolean isEmpty()

返回map是否為空

public V get(Object key)

通過(guò)調(diào)用內(nèi)部方法getNode來(lái)獲取數(shù)據(jù)

final Node<K, V> getNode(int hash, Object key)

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  • 如果table為null或者長(zhǎng)度為0,或者計(jì)算的index的對(duì)應(yīng)值為null览闰,則返回null(沒(méi)有匹配)
  • 否則檢查第一個(gè)值芯肤,如果keys也相等則說(shuō)明找到,返回
  • 否則檢查是不是TreeNode压鉴,如果是則調(diào)用getTreeNode
  • 否則遍歷鏈表后找到值后返回

public boolean containsKey(Object key)

調(diào)用內(nèi)部方法getNode崖咨,判斷是否包含key

public V put(K key, V value)

調(diào)用內(nèi)部方法putVal來(lái)實(shí)現(xiàn)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

很重要,用來(lái)往table里放東西的方法晴弃。

  • 檢查table掩幢,必要時(shí)做初始化(resize)
  • 計(jì)算index逊拍,如果不和任何hash沖突,則新建Node
  • 否則查看是否是同一個(gè)key(hash也相同)际邻,如果是則覆寫
  • 否則發(fā)生撞庫(kù)芯丧,判斷是否是TreeNode,如果是則調(diào)用putTreeval方法
  • 否則遍歷鏈表世曾,如果在鏈表中有相同的hash和key缨恒,則覆寫
  • 否則插入鏈表鏈尾,如果插入后超過(guò)閾值轮听,則Treeify
  • 如果onlyIfAbsent是true的話骗露,則不更新舊值
  • 如果size大于閾值,則調(diào)用resize

onlyIfAbsent: 只有當(dāng)不存在時(shí)才更新

evict:

還有一些鉤子函數(shù)

  • afterNodeAccess
  • afterNodeInsertion

final Node<K, V>[] resize()

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

resize是很重要的一個(gè)內(nèi)部使用的函數(shù)血巍,

主要是用來(lái)處理數(shù)據(jù)結(jié)構(gòu)的初始化萧锉,以及在增刪Node的時(shí)候,

通過(guò)resize來(lái)保持?jǐn)?shù)據(jù)結(jié)構(gòu)的合理性述寡。

table為內(nèi)部保存數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)柿隙。

流程的大概總結(jié):

  • 計(jì)算當(dāng)前容量,如果已經(jīng)大于等于最大容量鲫凶,則沒(méi)必要resize禀崖,直接返回
  • 如果當(dāng)前容量的兩倍小于最大容量,并且大于初始容量螟炫,則當(dāng)前閾值和容量翻倍
  • 如果容量為零波附,但是閾值不為零,則將容量設(shè)置為閾值的值
  • 否則的話則初始化容量和閾值
  • 如果閾值還是零的話昼钻,則用當(dāng)前閾值和負(fù)載因子計(jì)算新的閾值

到此為止掸屡,閾值和容量的事情我們搞定了

下面開始把舊的table中的Node放到新的table中去。

  • 對(duì)于每一個(gè)table中的Node换吧,計(jì)算新的index
  • 還是老樣子折晦,如果是單個(gè)節(jié)點(diǎn),則直接放進(jìn)新table中
  • 如果是TreeNode沾瓦,則調(diào)用tree的方法满着,split
  • 否則插入鏈表,因?yàn)閞esize之后贯莺,每個(gè)元素都可能出現(xiàn)在不同的位置上风喇,所以判斷插入的位置,然后建立兩個(gè)鏈表

判斷方式:(e.hash & oldCap) == 0

解釋:因?yàn)閞esize之后缕探,左移一位魂莫,相當(dāng)于最高位左移一位,因此如果e.hash & oldCap為零的話爹耗,

說(shuō)明e.hash的高位上沒(méi)有1耙考,因此就算newCap和e.hash并運(yùn)算結(jié)果也不會(huì)改變谜喊。

用此方法可以判斷每個(gè)e到底屬于哪個(gè)鏈表,

只可能有兩種倦始,一種是保持原來(lái)的鏈表斗遏,一種是進(jìn)入新的鏈表。

final void treeifyBin(Node<K, V>[] tab, int hash)

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

鏈表到樹結(jié)構(gòu)的轉(zhuǎn)換

  • 如果傳入的tab不符合要求鞋邑,則resize
  • 否則诵次,計(jì)算index找到對(duì)應(yīng)元素,開始treeify
  • 調(diào)用內(nèi)部方法replacementTreeNode新建一個(gè)TreeNode
  • 創(chuàng)建樹頭hd枚碗,之后對(duì)于新的節(jié)點(diǎn)逾一,他的prev指向上一輪的舊節(jié)點(diǎn),舊節(jié)點(diǎn)的next指向新節(jié)點(diǎn)
  • 創(chuàng)建紅黑樹肮雨,treeify遵堵,之后細(xì)講

public void putAll(Map<? extends K, ? extends V> m)

調(diào)用內(nèi)部的函數(shù)putMapEntries

public V remove(Object key)

繼續(xù)封裝怨规,調(diào)用內(nèi)部的removeNode方法鄙早。

返回被刪除的元素

final Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean moveable)

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

參數(shù)好多。椅亚。

先說(shuō)下參數(shù)

  • hash,對(duì)應(yīng)的哈希值
  • key舱污,對(duì)應(yīng)的key值呀舔,這個(gè)是唯一的
  • value,對(duì)應(yīng)的value
  • matchValue扩灯,配合value使用的媚赖,只刪除value也相同的Node
  • moveable,用在tree中

具體的邏輯

  • 如果符合條件(table不為空珠插,hash對(duì)應(yīng)的位置有數(shù)據(jù)之類)惧磺,則正式開始
  • 找到要?jiǎng)h除的Node,和putVal差不多捻撑,單值磨隘,紅黑樹或者鏈表,找到為止
  • 如果找到的node不為空顾患,且value相同(matchValue)番捂,則根據(jù)node類型刪除
  • 如果是TreeNode,調(diào)用removeTreeNode方法
  • 如果是firstNode江解,則tabl[index] = node.next
  • 否則p.next = node.next设预,經(jīng)典的鏈表刪除元素的方法

鉤子函數(shù):afterNodeRemoval

public void clear()

清空所有的元素,tab[i] = null

public boolean containsValue(Object value)

因?yàn)槊恳粋€(gè)Node都有next屬性犁河,

所以對(duì)table遍歷鳖枕,再對(duì)node遍歷即可找到是否包含value的node魄梯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宾符,隨后出現(xiàn)的幾起案子酿秸,更是在濱河造成了極大的恐慌,老刑警劉巖吸奴,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允扇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡则奥,警方通過(guò)查閱死者的電腦和手機(jī)考润,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)读处,“玉大人糊治,你說(shuō)我怎么就攤上這事》2眨” “怎么了井辜?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)管闷。 經(jīng)常有香客問(wèn)我粥脚,道長(zhǎng),這世上最難降的妖魔是什么包个? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任刷允,我火速辦了婚禮,結(jié)果婚禮上碧囊,老公的妹妹穿的比我還像新娘树灶。我一直安慰自己,他們只是感情好糯而,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布天通。 她就那樣靜靜地躺著,像睡著了一般熄驼。 火紅的嫁衣襯著肌膚如雪像寒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天谜洽,我揣著相機(jī)與錄音萝映,去河邊找鬼。 笑死阐虚,一個(gè)胖子當(dāng)著我的面吹牛序臂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奥秆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逊彭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起构订,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侮叮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后悼瘾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囊榜,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年亥宿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卸勺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烫扼,死狀恐怖曙求,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情映企,我是刑警寧澤悟狱,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站堰氓,受9級(jí)特大地震影響挤渐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜双絮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一挣菲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掷邦,春花似錦、人聲如沸椭赋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春辅斟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聘萨。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工课竣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叉信。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓亩冬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子硅急,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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

  • 1.HashMap是一個(gè)數(shù)組+鏈表/紅黑樹的結(jié)構(gòu)覆享,數(shù)組的下標(biāo)在HashMap中稱為Bucket值,每個(gè)數(shù)組項(xiàng)對(duì)應(yīng)的...
    誰(shuí)在烽煙彼岸閱讀 1,025評(píng)論 2 2
  • 摘要 HashMap是Java程序員使用頻率最高的用于映射(鍵值對(duì))處理的數(shù)據(jù)類型营袜。隨著JDK(Java Deve...
    周二倩你一生閱讀 1,250評(píng)論 0 5
  • 前言 這次我和大家一起學(xué)習(xí)HashMap撒顿,HashMap我們?cè)诠ぷ髦薪?jīng)常會(huì)使用,而且面試中也很頻繁會(huì)問(wèn)到荚板,因?yàn)樗?..
    liangzzz閱讀 7,990評(píng)論 7 102
  • 一 兩年前的今天 我從沙河跑到學(xué)院路 就站在某公寓樓下 看白楊高聳 柳樹低垂 看冬青翠綠 銀杏金黃 一抬頭 那個(gè)身...
    自唯至熟閱讀 570評(píng)論 0 0
  • 在做項(xiàng)目的時(shí)候凤壁,如果項(xiàng)目是前后分離的,后端一定要和前端或者是移動(dòng)端對(duì)接接口跪另,那么問(wèn)題來(lái)了拧抖,接口是不是要自己寫給他們...
    大豬大豬閱讀 298評(píng)論 0 1