HashMap源碼初探

附圖:

集合架構(gòu):


2608446-e941e68f81f5448c.jpg

map架構(gòu):

20161027225853859.png

一:前言

HashMap想必是所有java開(kāi)發(fā)人員最常用的集合操作類之一椭坚,在日常開(kāi)發(fā)中我們最常用的雙列(key-value)集合便是hashmap,它是一個(gè)采用哈希表實(shí)現(xiàn)的鍵值對(duì)集合,是一種<key,value>的存儲(chǔ)結(jié)構(gòu),繼承自 [AbstractMap]迅细,實(shí)現(xiàn)了 [Map 接口]。它特殊的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)致使他能在眾多的集合操作類中脫穎而出咆疗,線性鏈表結(jié)構(gòu)讓它在保證了查詢效率的同時(shí)也兼顧了增刪的效率波丰,簡(jiǎn)單方便的API操作可以完成各種復(fù)雜的數(shù)據(jù)業(yè)務(wù),成為最受歡迎的集合操作類。

二:為什么會(huì)出現(xiàn)HashMap:

我們知道莺戒,在數(shù)據(jù)結(jié)構(gòu)中伴嗡,數(shù)組結(jié)構(gòu)可以保證元素的檢索效率,但是在添加和刪除的功能上就顯得略微遲鈍了从铲,例如java中的ArrayList瘪校。在檢索元素的時(shí)候,只需要根據(jù)索引直接檢索名段,無(wú)需去遍歷整個(gè)集合阱扬,但是如果要增加或刪除元素,就必須去重新維護(hù)索引伸辟。另一種數(shù)據(jù)結(jié)構(gòu)鏈表就恰恰相反麻惶,增加或刪除元素只需要修改引用,而由于沒(méi)有索引信夫,它在檢索元素的時(shí)候就必須從頭到尾的去遍歷整個(gè)集合窃蹋,最具有代表性的就是java中的linkedList。當(dāng)然静稻,在實(shí)際開(kāi)發(fā)中完全可以根據(jù)業(yè)務(wù)需求去選擇更適合的工具類去操作數(shù)據(jù)(多檢索動(dòng)作的就選用底層是數(shù)組結(jié)構(gòu)的集合類脐彩,多增刪動(dòng)作的就選用底層是鏈表結(jié)構(gòu)的集合類)。但是如果檢索和增刪操作都很頻繁呢姊扔?這時(shí)候,HashMap就應(yīng)運(yùn)而生了梅誓,毫無(wú)疑問(wèn)它是數(shù)組和鏈表的組合體(線性鏈表結(jié)構(gòu))恰梢,集數(shù)組與鏈表的優(yōu)點(diǎn)為一身。我們先來(lái)解釋一下“線性鏈表”這個(gè)名詞吧梗掰,其本質(zhì)就是一個(gè)元素為鏈表的數(shù)組嵌言。

三:數(shù)據(jù)結(jié)構(gòu)(線性鏈表):

HashMap的存儲(chǔ)結(jié)構(gòu):哈希表有多種不同的實(shí)現(xiàn)方法,HashMap采用的是一種常用的實(shí)現(xiàn)方法——拉鏈法及穗,我們可以理解為“鏈表的數(shù)組”摧茴。

210003116887371.png

從上圖可以看出HashMap是Y軸方向是數(shù)組,X軸方向就是鏈表的存儲(chǔ)方式埂陆。大家都知道數(shù)組的存儲(chǔ)方式在內(nèi)存的地址是連續(xù)的苛白,大小固定,一旦分配不能被其他引用占用焚虱。它的特點(diǎn)是查詢快购裙,時(shí)間復(fù)雜度是O(1),插入和刪除的操作比較慢鹃栽,時(shí)間復(fù)雜度是O(n)躏率,鏈表的存儲(chǔ)方式是非連續(xù)的,大小不固定,特點(diǎn)與數(shù)組相反薇芝,插入和刪除快蓬抄,查詢速度慢。HashMap可以說(shuō)是一種折中的方案吧夯到。

數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)主要包括:順序存儲(chǔ)嚷缭,鏈?zhǔn)酱鎯?chǔ),索引存儲(chǔ)黄娘,散列存儲(chǔ)峭状。
這里注意:hashmap的鏈表中的每一個(gè)元素是一個(gè)Entry對(duì)象,包括key和value逼争,當(dāng)然還有其他的東西优床,而非只有鍵和值。

四:架構(gòu):

本圖引自武皇帝博客


202221148131465.png

五:特點(diǎn):

(1):底層實(shí)現(xiàn)是 鏈表數(shù)組誓焦,JDK 8 后又加了 紅黑樹(shù)
(2):實(shí)現(xiàn)了 Map 全部的方法
(3):key 用 Set 存放胆敞,所以想做到 key 不允許重復(fù),key 對(duì)應(yīng)的類需要重寫(xiě) hashCode 和 equals 方法
(4):允許空鍵和空值(但空鍵只有一個(gè)杂伟,且放在第一位)
(5):元素是無(wú)序的移层,而且順序會(huì)不定時(shí)改變
(6):插入、獲取的時(shí)間復(fù)雜度基本是 O(1)(前提是有適當(dāng)?shù)墓:瘮?shù)赫粥,讓元素分布在均勻的位置)(7):遍歷整個(gè) Map 需要的時(shí)間與 桶(數(shù)組) 的長(zhǎng)度成正比(因此初始化時(shí) HashMap 的容量不宜太大)
(8):兩個(gè)關(guān)鍵因子:初始容量观话、加載因子
(9):除了不允許 null 并且同步,Hashtable 幾乎和他一樣越平。

六:原理:

1频蛔、首先判斷Key是否為Null,如果為null秦叛,直接查找Enrty[0]晦溪,如果不是Null,先計(jì)算KeyHashCode挣跋,然后經(jīng)過(guò)二次Hash三圆。得到Hash值,這里的Hash特征值是一個(gè)int值避咆。
2舟肉、根據(jù)Hash值,要找到對(duì)應(yīng)的數(shù)組啊查库,所以對(duì)Entry[]的長(zhǎng)度length求余度气,得到的就是Entry數(shù)組的index。
3膨报、找到對(duì)應(yīng)的數(shù)組磷籍,就是找到了所在的鏈表适荣,然后按照鏈表的操作對(duì)Value進(jìn)行插入、刪除和查詢操作院领。

七:源碼解析:

一下源碼出自JDK1.7

7.1:重要屬性
    /**
     * The default initial capacity - MUST be a power of two.
     * 默認(rèn)的初始化容量弛矛,必須是2的指數(shù)。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量 1<<30.必須是2的指數(shù)比然。
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     * 默認(rèn)的負(fù)載因子丈氓,值是0.75f(跟數(shù)據(jù)結(jié)構(gòu)中的hash有關(guān))。
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * An empty table instance to share when the table is not inflated.
     * 一個(gè)空的entry數(shù)組
     */
    static final Entry<?, ?>[] EMPTY_TABLE = {};

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     * Entry數(shù)組强法,哈希表万俗,長(zhǎng)度必須為2的冪  
     */
    transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;

    /**
     * The number of key-value mappings contained in this map.
     * 已存元素的個(gè)數(shù)  
     */
    transient int size;

    /**
     * The next size value at which to resize (capacity * load factor).
     * 下次擴(kuò)容的臨界值,size>=threshold就會(huì)擴(kuò)容 
     */
    int threshold;

    /**
     * The load factor for the hash table.
     * 加載因子  
     */
    final float loadFactor;

    /**
     * HashMap被改變的次數(shù)
     */
    transient int modCount;
7.2構(gòu)造方法
    /**
     * 無(wú)參構(gòu)造(默認(rèn)容量16饮怯,默認(rèn)加載因子0.75)
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 帶參構(gòu)造(給出初識(shí)容量闰歪,加載因子使用默認(rèn)0.75)
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

   /**
     * 真正的初始化
     * @param initialCapacity
     * @param loadFactor
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //如果初始化容量小于0,拋出異常
        if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        //如果初始化容量大于最大容量1<<30蓖墅,則令其等于1<<30
        if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;
        //如果加載因子小于等于0或者不是數(shù)字库倘,則拋出異常
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        //賦值成員變量
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        //初始化
        init();
    }
7.3:靜態(tài)內(nèi)部類Entry
    /**
     * ClassName: Entry 靜態(tài)內(nèi)部類
     */
    static class Entry<K, V> implements Map.Entry<K, V> {
        final K key;//key
        V value;//value
        Entry<K, V> next;//下一個(gè)元素
        int hash;//hash值

        /**
         * 創(chuàng)建一個(gè)Entry
         */
        Entry(int h, K k, V v, Entry<K, V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        //獲取key
        public final K getKey() {
            return key;
        }
        //獲取value
        public final V getValue() {
            return value;
        }
        //設(shè)置value
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //與另一個(gè)Entry對(duì)象比較
        public final boolean equals(Object o) {
            //先判斷是否是entry對(duì)象類型
            if (!(o instanceof Map.Entry)) return false;
            Map.Entry e = (Map.Entry) o;//獲取到需要比較的entry對(duì)象
            Object k1 = getKey();//獲取當(dāng)前Ehtry的key值
            Object k2 = e.getKey();//獲取要比較Entry的key值
            //比較兩者的key值
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                //同樣的方法比較value值
                Object v1 = getValue();
                Object v2 = e.getValue();
                //如果key和value都相等,那兩個(gè)Entry就相等
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            //否則论矾,不等
            return false;
        }
        //獲取hash值
        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }
        //tostring
        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is overwritten
         * by an invocation of put(k,v) for a key k that's already in the
         * HashMap.
         */
        void recordAccess(HashMap<K, V> m) {
        }

        /**
         * This method is invoked whenever the entry is removed from the table.
         */
        void recordRemoval(HashMap<K, V> m) {
        }
    }

HashMap底層維護(hù)的Entry類型的數(shù)組教翩,每個(gè)元素都是Entry類型,Entry類型會(huì)維護(hù)兩個(gè)信息贪壳,一個(gè)是key的信息饱亿,一個(gè)是value的信息,而key跟value正好是我們往HashMap里面put的那個(gè)key跟value闰靴。還有一個(gè)Entry類型的引用路捧,用于指向下一個(gè)Entry類型的引用,還有一個(gè)hash哈希值,就是位置传黄。

7.4:添加元素

在看添加元素之前,我們先要了解hashmap到底是怎樣存儲(chǔ)元素的队寇。首先我們知道HashMap里面實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類Entry膘掰,這個(gè)上面的代碼已經(jīng)說(shuō)明了,其重要的屬性有 key , value, next佳遣,從屬性key,value我們就能很明顯的看出來(lái)Entry就是HashMap鍵值對(duì)實(shí)現(xiàn)的一個(gè)基礎(chǔ)bean识埋,我們上面說(shuō)到HashMap的基礎(chǔ)就是一個(gè)線性數(shù)組,這個(gè)數(shù)組就是Entry[]零渐,Map里面的內(nèi)容都保存在Entry[]里面窒舟。理解了這些,我們?cè)賮?lái)看它put的源碼就稍微容易點(diǎn)了诵盼,直接上代碼

    /**
     * 添加元素
     */
    public V put(K key, V value) {
        //如果這個(gè)table是一個(gè)空的entry數(shù)組惠豺,(table指成員變量transient Entry<K, V>[] table)
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);//就以默認(rèn)加載因子創(chuàng)建一個(gè)entry
        }
        //如果key是null的(hashmap是允許一個(gè)null鍵的)
        if (key == null)
            return putForNullKey(value);
        
        //當(dāng)key不為null
        int hash = hash(key);//計(jì)算key的hash值
        int i = indexFor(hash, table.length);//根據(jù)哈希值和table數(shù)組的長(zhǎng)度定位數(shù)組項(xiàng)(取模)
        
        /**
         * 對(duì)數(shù)組項(xiàng)的鏈表進(jìn)行遍歷
         */
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果key的哈希值與鏈表中的某一項(xiàng)的哈希值相等且key本身引用值相等或者引用值所指向的對(duì)象相等
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //則替換相應(yīng)項(xiàng)的value值為新的value银还,并返回老的value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //如果沒(méi)有找到相同的key,則加入該項(xiàng)到鏈表中
        modCount++;
        addEntry(hash, key, value, i);//此方法直接將新的項(xiàng)加入到鏈表的頭部洁墙,新項(xiàng)的next引用指向原來(lái)的鏈表項(xiàng)
        return null;
    }
public V put(K key, V value)

添加方法是hashMap的精髓所在蛹疯,我們來(lái)看看他具體都做了哪些事情,
(第一步):先判斷成員變量table(即entry數(shù)組)是否為EMPTY_TABLE空數(shù)組热监,如果是捺弦,就調(diào)用 inflateTable(threshold) 方法以默認(rèn)加載因子為參數(shù)創(chuàng)建一個(gè)新的Entry數(shù)組。反之則代碼繼續(xù)運(yùn)行孝扛。

(第二步):判斷這個(gè)key是否為null(hashMap是運(yùn)行null鍵的)列吼,如果put的鍵是null,則執(zhí)行putForNullKey(value) 方法苦始。我們來(lái)看看這個(gè)方法:

private V putForNullKey(V value) {
        //如果事先已經(jīng)存在key為null的映射寞钥,則替換后返回old 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;
            }
        }
        //如果不存在盈简,則添加新的項(xiàng)到鏈表中
        modCount++;
        //addEntry(int hash, K key, V value, int bucketIndex)我們發(fā)現(xiàn)當(dāng)key為null時(shí)凑耻,此Entry始終放在數(shù)組的第一個(gè)索引0處
        addEntry(0, null, value, 0);
        return null;
    }

這個(gè)方法是對(duì)key為null的情況作了一個(gè)特殊處理,當(dāng)事先已經(jīng)有key為null的映射時(shí)柠贤,用新的value代替舊的value香浩,并將舊的value返回;當(dāng)事先沒(méi)有key為null的映射時(shí)臼勉,則是直接調(diào)用 addEntry(0, null, value, 0)方法邻吭,注意這個(gè)方法的參數(shù),我們發(fā)現(xiàn)當(dāng)key為null時(shí)宴霸,此Entry始終放在數(shù)組的第一個(gè)索引0處囱晴。

(第三步):如果key不為null,則先算出key的hash值瓢谢,再根據(jù)hash值和table數(shù)組的長(zhǎng)度進(jìn)行取莫運(yùn)算畸写,找到Entry要存放的索引位置(這時(shí)也就找到對(duì)應(yīng)的鏈表了)

(第四步):遍歷這個(gè)index索引處的鏈表,尋找鏈表中所有的key氓扛,如果key已存在枯芬,則用新的value替換舊的value,并將舊的value返回采郎。我們來(lái)看看代碼:

    /**
         * 對(duì)數(shù)組項(xiàng)的鏈表進(jìn)行遍歷
         */
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果key的哈希值與鏈表中的某一項(xiàng)的哈希值相等且key本身引用值相等或者引用值所指向的對(duì)象相等
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //則替換相應(yīng)項(xiàng)的value值為新的value千所,并返回老的value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

(第五步):如果第四步?jīng)]有找到相同的key,則證明這個(gè)鏈表中此key的映射還不存在蒜埋,這時(shí)候淫痰,我們便可以直接去執(zhí)行添加Entry,并將最新的Entry放在鏈表頭部整份,將其引用指向舊的Entry待错,返回null籽孙。

20160825173336382.png

上面我們大致已經(jīng)了解了hashmap元素的實(shí)現(xiàn),但是這里遺漏一個(gè)問(wèn)題朗鸠,我們都知道hashmap的key是唯一的蚯撩,并且大部分同學(xué)也都知道是通過(guò)hashCode和equals方法來(lái)實(shí)現(xiàn)的,那么它到底是如何實(shí)現(xiàn)key唯一的呢烛占?我們大可先了解了解hashcode和equal胎挎。

我們首先想到的是用equals比較,沒(méi)錯(cuò)忆家,這樣可以實(shí)現(xiàn)犹菇,但隨著內(nèi)部元素的增多,put和get的效率將越來(lái)越低芽卿,這里的時(shí)間復(fù)雜度是O(n)揭芍,假如有1000個(gè)元素,put時(shí)需要比較1000次卸例。實(shí)際上称杨,HashMap很少會(huì)用到equals方法,因?yàn)槠鋬?nèi)通過(guò)一個(gè)哈希表管理所有元素筷转,當(dāng)我們調(diào)用put存值時(shí)姑原,HashMap首先會(huì)調(diào)用K的hashCode方法,獲取哈希碼呜舒,通過(guò)哈希碼快速找到某個(gè)存放位置锭汛,這個(gè)位置可以被稱之為bucketIndex,通過(guò)上面所述hashCode的協(xié)定可以知道袭蝗。

如果hashCode不同唤殴,equals一定為false,如果hashCode相同到腥,equals不一定為true朵逝。

所以理論上,hashCode可能存在沖突的情況乡范,有個(gè)專業(yè)名詞叫碰撞配名,當(dāng)碰撞發(fā)生時(shí),計(jì)算出的bucketIndex也是相同的篓足,這時(shí)會(huì)取到bucketIndex位置已存儲(chǔ)的元素,最終通過(guò)equals來(lái)比較闰蚕,equals方法就是哈希碼碰撞時(shí)才會(huì)執(zhí)行的方法栈拖,所以前面說(shuō)HashMap很少會(huì)用到equals。

20160825165407067.jpg

現(xiàn)在我們知道没陡,執(zhí)行put方法后涩哟,最終HashMap的存儲(chǔ)結(jié)構(gòu)會(huì)有這三種情況索赏,情形3是最少發(fā)生的,哈希碼發(fā)生碰撞屬于小概率事件贴彼。到目前為止潜腻,我們了解了兩件事:

(1):HashMap通過(guò)鍵的hashCode來(lái)快速的存取元素。
(2):當(dāng)不同的對(duì)象hashCode發(fā)生碰撞時(shí)器仗,HashMap通過(guò)單鏈表來(lái)解決融涣,將新元素加入鏈表表頭,通過(guò)next指向原有的元素精钮。單鏈表在Java中的實(shí)現(xiàn)就是對(duì)象的引用(復(fù)合)威鹿。

這里添加一個(gè)小的知識(shí)點(diǎn)(時(shí)間復(fù)雜度與空間復(fù)雜度)
時(shí)間復(fù)雜度:時(shí)間頻度 一個(gè)算法執(zhí)行所耗費(fèi)的時(shí)間,其評(píng)判標(biāo)準(zhǔn)以時(shí)間長(zhǎng)短為主
空間復(fù)雜度:空間頻度 一個(gè)算法執(zhí)行所消耗的內(nèi)存空間轨香,其評(píng)判標(biāo)準(zhǔn)以內(nèi)存空間為主

相關(guān)博文:http://blog.csdn.net/booirror/article/details/7707551/

7.5元素獲取
    /**
     * 元素獲取
     */
    public V get(Object key) {
        //先判斷key是否為null
        if (key == null)
            return getForNullKey();
        //根據(jù)key獲取對(duì)應(yīng)的entry
        Entry<K, V> entry = getEntry(key);
        //返回value
        return null == entry ? null : entry.getValue();
    }

元素的獲取看起來(lái)很簡(jiǎn)單忽你,大致分為三步
(第一步):先判斷此key是否為null,如果不為null則繼續(xù)向下執(zhí)行臂容,反之調(diào)用 getForNullKey() 方法科雳,這也是一個(gè)經(jīng)過(guò)處理的方法,來(lái)看看方法實(shí)現(xiàn)

    private V getForNullKey() {
        //判斷map大小
        if (size == 0) {
            return null;
        }
        //直接獲取table[0]
        for (Entry<K, V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

上邊說(shuō)過(guò)了脓杉,當(dāng)put是key為null則直接存入table數(shù)據(jù)的0索引處糟秘,所以取的時(shí)候當(dāng)然也從0索引處取

(第二步):根據(jù)key獲取對(duì)應(yīng)的Entry對(duì)象

    final Entry<K, V> getEntry(Object key) {
        //先判斷map的大小
        if (size == 0) {
            return null;
        }
        //計(jì)算key的hash值
        int hash = (key == null) ? 0 : hash(key);
        //遍歷當(dāng)前hash列所對(duì)應(yīng)的鏈表,并根據(jù)key查找Entry對(duì)象并返回
        for (Entry<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;
    }

(第三步):當(dāng)找到對(duì)應(yīng)的Entry時(shí)丽已,則返回Entry中的value值蚌堵,反正返回null即沒(méi)有找到

線程安全:

測(cè)試代碼

/**
 * ClassName: TestHashMap
 * @author lvfang
 * @Desc: TODO
 * @date 2017-9-23
 */
public class TestHashMap implements Runnable {
    
    private HashMap<Integer, Integer> map = null;
    
    public TestHashMap(HashMap<Integer, Integer> map){
        this.map = map;
    }
    
    @Override
    public void run() {
        for(int i=0 ; i<50 ; i++) map.put(i, i);
        System.out.println(map.size());
    }

    public static void main(String[] args) throws InterruptedException {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(1, 1);
        map.get(1);
        
        for(int i=0 ; i<5 ; i++){
            new Thread(new TestHashMap(map)).start();
        }
    }   
}

上邊的例子很明顯,開(kāi)啟5個(gè)線程沛婴,每個(gè)線程對(duì)map中都添加key為0-50的鍵和值吼畏,由于hashmap的key是唯一的,所以最終map的大小還是50嘁灯,而運(yùn)行結(jié)果則不定泻蚊。

為什么會(huì)產(chǎn)生線程安全:
HashMap在并發(fā)時(shí)可能出現(xiàn)的問(wèn)題主要是兩方面,首先如果多個(gè)線程同時(shí)使用put方法添加元素,而且假設(shè)正好存在兩個(gè)put的key發(fā)生了碰撞(hash值一樣)丑婿,那么根據(jù)HashMap的實(shí)現(xiàn)性雄,這兩個(gè)key會(huì)添加到數(shù)組的同一個(gè)位置,這樣最終就會(huì)發(fā)生其中一個(gè)線程的put的數(shù)據(jù)被覆蓋羹奉。第二就是如果多個(gè)線程同時(shí)檢測(cè)到元素個(gè)數(shù)超過(guò)數(shù)組大小*loadFactor秒旋,這樣就會(huì)發(fā)生多個(gè)線程同時(shí)對(duì)Node數(shù)組進(jìn)行擴(kuò)容,都在重新計(jì)算元素位置以及復(fù)制數(shù)據(jù)诀拭,但是最終只有一個(gè)線程擴(kuò)容后的數(shù)組會(huì)賦給table迁筛,也就是說(shuō)其他線程的都會(huì)丟失,并且各自線程put的數(shù)據(jù)也丟失耕挨。

解決方案一:方法同步必然是不可少的或者Hashtable细卧;Map<String, String> hashtable = new Hashtable<>();

解決方案二:用collections創(chuàng)建一個(gè)線程安全的map尉桩;Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());

解決方案三:java并發(fā)包下的ConcurrentHashMap;Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

面試題:

http://www.cnblogs.com/zywu/p/5753736.html
http://blog.csdn.net/song19890528/article/details/16891015
http://www.cnblogs.com/lchzls/p/6714474.html

相關(guān)博文:

http://www.importnew.com/21396.html
http://blog.csdn.net/happy_horse/article/details/52316865
http://www.cnblogs.com/wuhuangdi/p/4175991.html
http://blog.csdn.net/jerryburning/article/details/47619491
http://blog.csdn.net/ghsau/article/details/16890151

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贪庙,隨后出現(xiàn)的幾起案子蜘犁,更是在濱河造成了極大的恐慌,老刑警劉巖止邮,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件这橙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡农尖,警方通過(guò)查閱死者的電腦和手機(jī)析恋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)盛卡,“玉大人助隧,你說(shuō)我怎么就攤上這事』祝” “怎么了并村?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)滓技。 經(jīng)常有香客問(wèn)我哩牍,道長(zhǎng),這世上最難降的妖魔是什么令漂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任膝昆,我火速辦了婚禮,結(jié)果婚禮上叠必,老公的妹妹穿的比我還像新娘荚孵。我一直安慰自己,他們只是感情好纬朝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布收叶。 她就那樣靜靜地躺著,像睡著了一般共苛。 火紅的嫁衣襯著肌膚如雪判没。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天隅茎,我揣著相機(jī)與錄音澄峰,去河邊找鬼。 笑死辟犀,一個(gè)胖子當(dāng)著我的面吹牛俏竞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胞此,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了跃捣?” 一聲冷哼從身側(cè)響起漱牵,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疚漆,沒(méi)想到半個(gè)月后酣胀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娶聘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年闻镶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丸升。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铆农,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狡耻,到底是詐尸還是另有隱情墩剖,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布夷狰,位于F島的核電站岭皂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沼头。R本人自食惡果不足惜爷绘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望进倍。 院中可真熱鬧土至,春花似錦、人聲如沸背捌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毡庆。三九已至坑赡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間么抗,已是汗流浹背毅否。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝇刀,地道東北人螟加。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捆探。 傳聞我的和親對(duì)象是個(gè)殘疾皇子然爆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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

  • 實(shí)際上,HashSet 和 HashMap 之間有很多相似之處黍图,對(duì)于 HashSet 而言曾雕,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,515評(píng)論 1 37
  • 一剖张、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,268評(píng)論 0 16
  • HashMap 是 Java 面試必考的知識(shí)點(diǎn),面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度揩环。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,669評(píng)論 9 107
  • 01 前段時(shí)間回老家丰滑,每天陪著寶寶看動(dòng)畫(huà)片顾犹,一開(kāi)始我是抗拒的。想當(dāng)年褒墨,我們小的時(shí)候蹦渣,機(jī)器貓、一休哥貌亭、忍者神龜柬唯、阿童...
    小海貍媽媽閱讀 452評(píng)論 0 0
  • 窗文/ 默默日子在此定格冰凍后裝在心里一片很小的空間一個(gè)寂靜的世界一扇黑白的窗窗欞上那枚釘掛著凝固的斷章曾經(jīng)滄海的...
    荔園默默閱讀 295評(píng)論 8 17