ConcurrentHashMap\HashMap put操作時key爲什麼要rehash

參考java併發編程的藝術一書中,對ConcurrentHashMap的講解

ConcurrentHashMap使用的是分段鎖Segment來保證不同的Segment區域互相不干擾,不存在鎖競爭關係,從而提升map的效率.

由於ConcurrentHashMap中存放的是Segment數組,每個Segment持有一個鎖,和HashEntry數組.

定位一個key應該在哪個segment中非常重要,如果大多數的key被定位到一個segment中,則這個機制的意義就不大了.因此要避免不同的hashcode被分配到同一個segment中去.

segment掩碼最終由於計算key在segment數組中的位置,他的值爲

segmentMask:segment數組長度-1

以put方法舉例(jdk版本1.7)

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

第6行中j即是定位segments位置的代碼.默認情況下segmentShift的值爲28,之所以無符號右移了28位,是因爲hash(key)中已經進行了取key.hashcode,多次左右移動

private int hash(Object k) {
    int h = hashSeed;

    if ((0 != h) && (k instanceof String)) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // Spread bits to regularize both segment and index locations,
    // using variant of single-word Wang/Jenkins hash.
    h += (h <<  15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h <<   3);
    h ^= (h >>>  6);
    h += (h <<   2) + (h << 14);
    return h ^ (h >>> 16);
}


下面直接用key.hashcode與掩碼mask(默認15)進行與有什麼後果呢

以下四個hashcode & 15的結果 (15的二進制位1111)

0001111 & 15 =15
0011111 & 15 =15
0111111 & 15 =15
1111111 & 15 =15

這樣就造成了只要低4位相同,則無論高位是否相同,最終結果都一樣,這樣的就造成了大量key被分配到同一個segment中.

採用rehash值算法後,j的值爲4,15,7,8就都不相同了


HashMap

由此推算HsahMap其實也做了小量reHash操作

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;

第6行中,其實hash(key)也做了簡單的rehash,避免大量key,分配到某一個Entry中

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}


總結:

ConcurrentHashMap和HashMap歸根結底,裏面都有一個數組,來存放Entry<K,V>,數組的大小是有限的.

一個key被映射到數組的哪個位置其實不重要,重要的是避免大量key映射到同一個位置.由於ConcurrentHashMap裏面位運算太多,以HashMap舉例,它拿到一個hash後,定位數組位置的算法是:

/**
 * Returns index for hash code h.
 */
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

以HashMap默認的length:16舉例, h&15就是最終的位置.h是int型,有32位,而15只有低4位不爲0,則在按位與的場景下,只要低4位相同,則總會獲取相同的位置下標.rehash就是爲了消除這種較高衝突的可能,根據某種算法,打亂低4位,最終等到不同的位置下標.當然,如果兩個h一樣,是肯定會分配到相同的位置下標的



新博客地址:http://www.cnblogs.com/windliu 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章