Java集合類—HashMap

Map集合.jpg

HashMap的四個(gè)關(guān)注點(diǎn)

關(guān)注點(diǎn) 結(jié)論
hashMap鍵值是否可以為空 key和value都允許為null
hashMap是否允許重復(fù)數(shù)據(jù) key重復(fù)會(huì)覆蓋罪治,value允許重復(fù)
hashMap是否有序 無序
是否線程安全 HashMap不是線程安全的竭翠,(Hashtable是線程安全的,其方法被synchronized包裹)

HashMap簡(jiǎn)介

  • HashMap 是一個(gè)散列表琼锋,它存儲(chǔ)的內(nèi)容是鍵值對(duì)(key-value)映射棵红。
  • HashMap 繼承于AbstractMap,實(shí)現(xiàn)了Map观蜗、Cloneable臊恋、java.io.Serializable接口。繼承關(guān)系如下
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
  • HashMap 的實(shí)現(xiàn)不是同步的墓捻,這意味著它不是線程安全的抖仅。注意:它的key、value都可以為null砖第。此外撤卢,HashMap中的映射不是有序的。

HashMap 的實(shí)例有兩個(gè)參數(shù)影響其性能:“初始容量” 和 “加載因子”梧兼。容量 是哈希表中桶的數(shù)量放吩,初始容量 只是哈希表在創(chuàng)建時(shí)的容量。加載因子 是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿的一種尺度羽杰。當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí)渡紫,則要對(duì)該哈希表進(jìn)行 rehash 操作(即重建內(nèi)部數(shù)據(jù)結(jié)構(gòu)),從而哈希表將具有大約兩倍的桶數(shù)考赛。

通常惕澎,默認(rèn)加載因子是 0.75, 這是在時(shí)間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷颜骤,但同時(shí)也增加了查詢成本(在大多數(shù) HashMap 類的操作中唧喉,包括 get 和 put 操作,都反映了這一點(diǎn))复哆。在設(shè)置初始容量時(shí)應(yīng)該考慮到映射中所需的條目數(shù)及其加載因子欣喧,以便最大限度地減少 rehash 操作次數(shù)。如果初始容量大于最大條目數(shù)除以加載因子梯找,則不會(huì)發(fā)生 rehash 操作唆阿。
HashMap有四個(gè)構(gòu)造方法,如下圖


HashMap構(gòu)造方法

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

畫了個(gè)示意圖锈锤,如下驯鳖,左邊的數(shù)組索引是根據(jù)key的hash值計(jì)算得到闲询,不同hash值有可能產(chǎn)生一樣的索引,即哈希沖突浅辙,此時(shí)采用鏈地址法處理哈希沖突扭弧,即將所有索引一致的節(jié)點(diǎn)構(gòu)成一個(gè)單鏈表;


Entry<k,v>數(shù)組

相比于之前的版本记舆,jdk1.8在解決哈希沖突時(shí)有了較大的變化鸽捻,當(dāng)鏈表長(zhǎng)度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹泽腮,以減少搜索時(shí)間御蒲。原本Map.Entry接口的實(shí)現(xiàn)類Entry改名為了Node。轉(zhuǎn)化為紅黑樹時(shí)改用另一種實(shí)現(xiàn)TreeNode诊赊。

HashMap源碼分析

 // 序列號(hào)
    private static final long serialVersionUID = 362498820763181265L;    
    // 默認(rèn)的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默認(rèn)的填充因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 當(dāng)桶(bucket)上的結(jié)點(diǎn)數(shù)大于這個(gè)值時(shí)會(huì)轉(zhuǎn)成紅黑樹
    static final int TREEIFY_THRESHOLD = 8; 
    // 當(dāng)桶(bucket)上的結(jié)點(diǎn)數(shù)小于這個(gè)值時(shí)樹轉(zhuǎn)鏈表
    static final int UNTREEIFY_THRESHOLD = 6;
    // 桶中結(jié)構(gòu)轉(zhuǎn)化為紅黑樹對(duì)應(yīng)的table的最小大小
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存儲(chǔ)元素的數(shù)組厚满,總是2的冪次倍
    transient Node<k,v>[] table; 
    // 存放具體元素的集
    transient Set<map.entry<k,v>> entrySet;
    // 存放元素的個(gè)數(shù),注意這個(gè)不等于數(shù)組的長(zhǎng)度碧磅。
    transient int size;
    // 每次擴(kuò)容和更改map結(jié)構(gòu)的計(jì)數(shù)器
    transient int modCount;   
    // 臨界值 當(dāng)實(shí)際大小(容量*填充因子)超過臨界值時(shí)碘箍,會(huì)進(jìn)行擴(kuò)容
    int threshold;
    // 填充因子,默認(rèn)值是7.5
    final float loadFactor;

構(gòu)造方法

  /**
     * 根據(jù)自定義的初始化容量和加載因子構(gòu)建一個(gè)空的HashMap.
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * 使用初始化容量和默認(rèn)加載因子(0.75).
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 使用默認(rèn)初始化大小(16)和默認(rèn)加載因子(0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * 用已有的Map構(gòu)造一個(gè)新的HashMap.
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

數(shù)據(jù)存取

put方法

//根據(jù)key值計(jì)算出hashcode鲸郊,
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

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

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // table未初始化或者長(zhǎng)度為0丰榴,進(jìn)行擴(kuò)容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // (n - 1) & hash 確定元素存放在哪個(gè)桶中,桶為空严望,新生成結(jié)點(diǎn)放入桶中(此時(shí)多艇,這個(gè)結(jié)點(diǎn)是放在數(shù)組中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已經(jīng)存在元素
    else {
        Node<K,V> e; K k;
        // 比較桶中第一個(gè)元素(數(shù)組中的結(jié)點(diǎn))的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 將第一個(gè)元素賦值給e像吻,用e來記錄
                e = p;
        // hash值不相等,即key不相等复隆;為紅黑樹結(jié)點(diǎn)
        else if (p instanceof TreeNode)
            // 放入樹中
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 為鏈表結(jié)點(diǎn)
        else {
            // 在鏈表最末插入結(jié)點(diǎn)
            for (int binCount = 0; ; ++binCount) {
                // 到達(dá)鏈表的尾部
                if ((e = p.next) == null) {
                    // 在尾部插入新結(jié)點(diǎn)
                    p.next = newNode(hash, key, value, null);
                    // 結(jié)點(diǎn)數(shù)量達(dá)到閾值拨匆,轉(zhuǎn)化為紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    // 跳出循環(huán)
                    break;
                }
                // 判斷鏈表中結(jié)點(diǎn)的key值與插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出循環(huán)
                    break;
                // 用于遍歷桶中的鏈表挽拂,與前面的e = p.next組合惭每,可以遍歷鏈表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值與插入元素相等的結(jié)點(diǎn)
        if (e != null) { 
            // 記錄e的value
            V oldValue = e.value;
            // onlyIfAbsent為false或者舊值為null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替換舊值
                e.value = value;
            // 訪問后回調(diào)
            afterNodeAccess(e);
            // 返回舊值
            return oldValue;
        }
    }
    // 結(jié)構(gòu)性修改
    ++modCount;
    // 實(shí)際大小大于閾值則擴(kuò)容
    if (++size > threshold)
        resize();
    // 插入后回調(diào)
    afterNodeInsertion(evict);
    return null;
}

在上述代碼中的第二行注釋中亏栈,table未初始化或者長(zhǎng)度為0台腥,進(jìn)行擴(kuò)容,會(huì)調(diào)用resize()方法绒北,代碼如下

final Node<K,V>[] resize() {
    // 當(dāng)前table保存
    Node<K,V>[] oldTab = table;
    // 保存table大小
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 保存當(dāng)前閾值 
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 之前table大小大于0
    if (oldCap > 0) {
        // 之前table大于最大容量
        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
    }
    // 之前閾值大于0
    else if (oldThr > 0)
        newCap = oldThr;
    // oldCap = 0并且oldThr = 0闷游,使用缺省值(如使用HashMap()構(gòu)造函數(shù)峻汉,之后再插入一個(gè)元素會(huì)調(diào)用resize函數(shù)贴汪,會(huì)進(jìn)入這一步)
    else {           
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 新閾值為0
    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"})
    // 初始化table
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 之前的table已經(jīng)初始化過
    if (oldTab != null) {
        // 復(fù)制元素,重新進(jìn)行hash
        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;
                    // 將同一桶中的元素根據(jù)(e.hash & oldCap)是否為0進(jìn)行分割休吠,分成兩個(gè)不同的鏈表扳埂,完成rehash
                    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;
}

說明:進(jìn)行擴(kuò)容,會(huì)伴隨著一次重新hash分配瘤礁,并且會(huì)遍歷hash表中所有的元素阳懂,是非常耗時(shí)的。在編寫程序中柜思,要盡量避免resize希太。

get方法

public V get(Object key) {
        Node<K,V> e;
        //這里直接調(diào)用的getNode方法
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // table已經(jīng)初始化,長(zhǎng)度大于0酝蜒,根據(jù)hash尋找table中的項(xiàng)也不為空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 桶中第一項(xiàng)(數(shù)組元素)相等
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 桶中不止一個(gè)結(jié)點(diǎn)
        if ((e = first.next) != null) {
            // 為紅黑樹結(jié)點(diǎn)
            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;
}
    

putAll方法

public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

    /**
     * Implements Map.putAll and Map constructor
     *
     * @param m the map
     * @param evict false when initially constructing this map, else
     * true (relayed to method afterNodeInsertion).
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict); // put核心方法
            }
        }
    }

參考文章
圖解集合HashMap :http://www.importnew.com/25049.html)http://www.importnew.com/25049.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市亡脑,隨后出現(xiàn)的幾起案子堕澄,更是在濱河造成了極大的恐慌,老刑警劉巖霉咨,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛙紫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡途戒,警方通過查閱死者的電腦和手機(jī)坑傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喷斋,“玉大人唁毒,你說我怎么就攤上這事⌒亲Γ” “怎么了浆西?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)顽腾。 經(jīng)常有香客問我近零,道長(zhǎng),這世上最難降的妖魔是什么抄肖? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任久信,我火速辦了婚禮,結(jié)果婚禮上漓摩,老公的妹妹穿的比我還像新娘裙士。我一直安慰自己,他們只是感情好幌甘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布潮售。 她就那樣靜靜地躺著痊项,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酥诽。 梳的紋絲不亂的頭發(fā)上鞍泉,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音肮帐,去河邊找鬼咖驮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛训枢,可吹牛的內(nèi)容都是我干的托修。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼恒界,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼睦刃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起十酣,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤涩拙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后耸采,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兴泥,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年虾宇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搓彻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘱朽,死狀恐怖旭贬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燥翅,我是刑警寧澤骑篙,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站森书,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谎势。R本人自食惡果不足惜凛膏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脏榆。 院中可真熱鬧猖毫,春花似錦、人聲如沸须喂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仔役,卻和暖如春掷伙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背又兵。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工任柜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沛厨。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓宙地,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逆皮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宅粥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 1. HashMap的數(shù)據(jù)結(jié)構(gòu) HashMap實(shí)際上是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),是數(shù)組與鏈表的結(jié)合體电谣。HashM...
    JohnShen閱讀 176評(píng)論 0 0
  • HashMap 源碼分析 前幾篇分析了 ArrayList 秽梅, LinkedList ,Vector 辰企,Stack...
    醒著的碼者閱讀 2,836評(píng)論 4 44
  • 前言 HashMap HashMap類繼承圖 HashMap屬性 HashMap構(gòu)造函數(shù)HashMap(int i...
    HikariCP閱讀 1,897評(píng)論 0 5
  • 我想要告訴你风纠,你是我的,永遠(yuǎn)都是我的牢贸。我愛的是許沂竹观,與任何事,與任何人潜索,無關(guān)臭增。 ?? To 齊 齊哥,你應(yīng)該覺得我...
    一方筮白閱讀 911評(píng)論 3 2
  • 6月9日的生日花,爭(zhēng)墻風(fēng)整陌。 爭(zhēng)墻風(fēng)基本上都是被看作為藤本植物拗窃,喜好攀援,尤其是在庭院中綠化栽植的時(shí)候泌辫,到處去随夸。 到...
    冬林探花閱讀 1,165評(píng)論 0 0