成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一起讀源碼之 — HashMap(jdk1.7)

ASCH / 1253人閱讀

摘要:關(guān)于不安全的問(wèn)題,感興趣的可以去看一下這篇文章老生常談,的死循環(huán)。

廢話不多說(shuō),直接進(jìn)入主題:

首先我們從構(gòu)造方法開(kāi)始:
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    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);
        // 初始化加載因子(默認(rèn)0.75f)
        this.loadFactor = loadFactor;
        // 初始化容器大小(默認(rèn)16)
        threshold = initialCapacity;
        init();
    }
    // 可以看到j(luò)dk1.7中hashMap的init方法并沒(méi)有創(chuàng)建hashMap的數(shù)組和Entry,
    // 而是移到了put方法里,后邊會(huì)講到
    void init() {
    }
最常用的put方法:
    public V put(K key, V value) {
        // 可以看到,初始化table是在首次put時(shí)開(kāi)始的
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // 對(duì)key為`null`的處理,進(jìn)入到方法里可以看到直接將其hash置為0,并插入到了數(shù)組下標(biāo)為0的位置
        if (key == null)
            return putForNullKey(value);
        // 計(jì)算hash值
        int hash = hash(key);
        // 根據(jù)hash,查找到數(shù)組對(duì)應(yīng)的下標(biāo)
        int i = indexFor(hash, table.length);
        // 遍歷數(shù)組第i個(gè)位置的鏈表
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            // 找到相同的key,并覆蓋其value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    
        modCount++;
        // 在table[i]下的鏈表中沒(méi)有找到相同的key,將entry加入到此鏈表
        // addEntry方法后邊會(huì)再看一下
        addEntry(hash, key, value, i);
        return null;
    }
根據(jù)put方法的流程,我們進(jìn)入到inflateTable方法看一下他的初始化代碼:
    // 容量一定為2的n次方,比如設(shè)置size=10,則容量則為大于10的且為2的n次方=16
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);
    
    // 計(jì)算擴(kuò)容臨界值:capacity * loadFactor,當(dāng)size>=threshold時(shí),觸發(fā)擴(kuò)容
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    // 初始化Entry數(shù)組
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
addEntry添加鏈表節(jié)點(diǎn)

能進(jìn)入到addEntry方法,說(shuō)明根據(jù)hash值計(jì)算出的數(shù)組下標(biāo)沖突,但是key不一樣

    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 當(dāng)數(shù)組的size >= 擴(kuò)容閾值,觸發(fā)擴(kuò)容,size大小會(huì)在createEnty和removeEntry的時(shí)候改變
        if ((size >= threshold) && (null != table[bucketIndex])) {
            // 擴(kuò)容到2倍大小,后邊會(huì)跟進(jìn)這個(gè)方法
            resize(2 * table.length);
            // 擴(kuò)容后重新計(jì)算hash和index
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        // 創(chuàng)建一個(gè)新的鏈表節(jié)點(diǎn),點(diǎn)進(jìn)去可以了解到是將新節(jié)點(diǎn)添加到了鏈表的頭部
        createEntry(hash, key, value, bucketIndex);
    }
resize擴(kuò)容
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 創(chuàng)建2倍大小的新數(shù)組
        Entry[] newTable = new Entry[newCapacity];
        // 將舊數(shù)組的鏈表轉(zhuǎn)移到新數(shù)組,就是這個(gè)方法導(dǎo)致的hashMap不安全,等下我們進(jìn)去看一眼
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        // 重新計(jì)算擴(kuò)容閾值(容量*加載因子)
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
get方法

對(duì)于put方法,get方法就很簡(jiǎn)單了

    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
    final Entry getEntry(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        // 根據(jù)hash值找到對(duì)應(yīng)的數(shù)組下標(biāo),并遍歷其E
        for (Entry 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;
    }
不安全的transfer方法
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        // 遍歷舊數(shù)組
        for (Entry e : table) {
            // 遍歷鏈表
            while(null != e) {
                Entry next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                // 計(jì)算節(jié)點(diǎn)在新數(shù)組中的下標(biāo)
                int i = indexFor(e.hash, newCapacity);
                // 將舊節(jié)點(diǎn)插入到新節(jié)點(diǎn)的頭部
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
這里粗略的講一下為什么transfer是不安全的

從上面的代碼可以看出,從oldTable中遍歷Entry是正序的,也就是a->b->c的順序,而插入到新數(shù)組的時(shí)候是采用的頭插法,也就是后插入的在首部,所以遍歷之后結(jié)果為c->b->a;

此時(shí)正常邏輯是沒(méi)有問(wèn)題的,而當(dāng)有多個(gè)線程同時(shí)進(jìn)行擴(kuò)容操作時(shí)就出現(xiàn)問(wèn)題了,看下邊的圖

此時(shí)的狀態(tài)為a線程創(chuàng)建了新數(shù)組,b線程也創(chuàng)建了新數(shù)組,同時(shí)b的cpu時(shí)間片用完進(jìn)入等待階段,


此時(shí)的狀態(tài)為a線程完成了數(shù)組的擴(kuò)容,退出了transfer方法,但是還沒(méi)有執(zhí)行下一句table = newTable;

b線程回來(lái)繼續(xù)執(zhí)行代碼

Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;

結(jié)果如下:

b會(huì)繼續(xù)執(zhí)行循環(huán)代碼,進(jìn)入到死循環(huán)狀態(tài)。

關(guān)于transfer不安全的問(wèn)題,感興趣的可以去看一下這篇文章老生常談,HashMap的死循環(huán)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75295.html

相關(guān)文章

  • ConcurrentHashMap基于JDK1.8源碼剖析

    摘要:下面我來(lái)簡(jiǎn)單總結(jié)一下的核心要點(diǎn)底層結(jié)構(gòu)是散列表數(shù)組鏈表紅黑樹(shù),這一點(diǎn)和是一樣的。是將所有的方法進(jìn)行同步,效率低下。而作為一個(gè)高并發(fā)的容器,它是通過(guò)部分鎖定算法來(lái)進(jìn)行實(shí)現(xiàn)線程安全的。 前言 聲明,本文用的是jdk1.8 前面章節(jié)回顧: Collection總覽 List集合就這么簡(jiǎn)單【源碼剖析】 Map集合、散列表、紅黑樹(shù)介紹 HashMap就是這么簡(jiǎn)單【源碼剖析】 LinkedHas...

    sanyang 評(píng)論0 收藏0
  • 集合源碼學(xué)習(xí)路---hashMap(jdk1.8)

    摘要:值得位數(shù)有的次方,如果直接拿散列值作為下標(biāo)訪問(wèn)主數(shù)組的話,只要算法比較均勻,一般是很難出現(xiàn)碰撞的。但是內(nèi)存裝不下這么大的數(shù)組,所以計(jì)算數(shù)組下標(biāo)就采取了一種折中的辦法,就是將得到的散列值與數(shù)組長(zhǎng)度做一個(gè)與操作。 hashMap簡(jiǎn)單介紹 hashMap是面試中的高頻考點(diǎn),或許日常工作中我們只需把hashMap給new出來(lái),調(diào)用put和get方法就完了。但是hashMap給我們提供了一個(gè)絕佳...

    kamushin233 評(píng)論0 收藏0
  • Java-Mysql你所需要的面試題集內(nèi)容

    摘要:注意排版不需要花花綠綠的,盡量使用語(yǔ)法。協(xié)議的長(zhǎng)連接和短連接,實(shí)質(zhì)上是協(xié)議的長(zhǎng)連接和短連接。長(zhǎng)連接短連接究竟是什么三次握手和四次揮手面試??蜑榱藴?zhǔn)確無(wú)誤地把數(shù)據(jù)送達(dá)目標(biāo)處,協(xié)議采用了三次握手策略。 一 簡(jiǎn)歷該如何寫 1.1 為什么說(shuō)簡(jiǎn)歷很重要?1.2-這3點(diǎn)你必須知道1.3-兩大法則了解一1.4-項(xiàng)目經(jīng)歷怎么寫?1.5-專業(yè)技能該怎么寫?1.6-開(kāi)源程序員簡(jiǎn)歷模板分享1.7 其他的一些...

    OpenDigg 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<