HashMap源碼解析(JDK1.7)

數據結構

  • table,Entry類型數組的數據
  • Entry,包括了key,value,Entry,以及hash
final K key;
V value;
Entry<K,V> next;
int hash;

put方法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);//見putForNullKey方法
    int hash = hash(key);
    int i = indexFor(hash, table.length);//見indexFor方法,取模
    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))) {//判斷hash值同樣,而且key也要同樣
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);//見addEntry方法
    return null;
}

putForNullKey方法

key爲空的狀況,在數組的第一個位置的鏈表遍歷查找,若是有key爲空,返回相應的值,若是沒有,添加到鏈表後面。數組

private V putForNullKey(V 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;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

indexFor方法

註釋已經提醒了,length長度必須是2的非0冪數,h & (length-1)是對h%length的意思(length長度爲2的非0冪數時有效)。好比123243423 % 16的值是15,123243423 & 15也是15,固然123243423是我隨便打的。取模主要是爲了可以平均的落在每一個數組上面。數據結構

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);
}

addEntry方法

void addEntry(int hash, K key, V value, int bucketIndex) {
    //擴容爲2倍長度,跟上面取模要求的同樣,乘以2也是2的非0冪數
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);//見createEntry方法
}

createEntry方法

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];//取到數組的位置的entry
    table[bucketIndex] = new Entry<>(hash, key, value, e);//新entry加到鏈表的頭部,並把數組指向新entry
    size++;
}
/**
 * Creates new entry.
 */
Entry(int h, K k, V v, Entry<K,V> n) {
    value = v;
    next = n;
    key = k;
    hash = h;
}

get方法

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);//見getEntry方法

    return null == entry ? null : entry.getValue();
}

getForNullKey方法

private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {//由於put的時候,直接放數組的第一個,因此查詢的時候,也查詢第一個
        if (e.key == null)
            return e.value;
    }
    return null;
}

getEntry方法

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);//先取hash
    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))))//判斷hash和key都相等
            return e;
    }
    return null;
}

transfer方法

這個方法在調用put的時候,在resize擴容的時候調用。在多線程的狀況下,會形成死循環。多線程

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;//把next先暫存
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

單線程狀況:

一、put("3",'a'),建立一個entry對象(參見createEntry方法),而後把引入給數組1的位置。
clipboard.png
二、put("0",'b'),
clipboard.png
三、put("7","a"),再建立一個entry對象,而後新對象的next指向舊的entry對象,最後數組指向新entry。隊頭插入的效率高,若是隊尾插入,還要遍歷鏈表。
clipboard.png
clipboard.pngthis

三、若是擴容到4,參加transfer方法,依然採用隊頭插入,也就是說,若是鏈表是1,2,3,4,那麼插入後就變成4,3,2,1。
現建立一個表,取到鏈表數據,開始遍歷。這裏假設都到index爲3的數組。
clipboard.png
clipboard.png
clipboard.png
clipboard.pngspa

多線程死循環狀況:

兩個線程同時擴容
clipboard.png
第一個線程,執行到下面代碼,此時E是7,Next是3,時間輪轉片到了。
第二個線程,也擴容,並執行完了,以下圖,左邊是第一線程,右邊是第二個線程。線程

e.next = newTable[i];
newTable[i] = e;
e = next;

clipboard.png
第一個線程往下執行,到最後,7的next是3,3的next是7
clipboard.png
clipboard.png3d

相關文章
相關標籤/搜索