1. 程式人生 > >HashTable原始碼講解之put(K key,V value)方法

HashTable原始碼講解之put(K key,V value)方法

我們知道HashTable在利用key值進行Enry<K,V>位置確定時常常會發生衝突,即通過雜湊函式計算出的陣列下標該位置上早已儲存有資料,此時解決衝突有兩種方式,一個是開放地址法,另一個是鏈地址法。開放地址法即遇到衝突時存放位置按系統的方法(線性探測、二次探測以及再雜湊法)在陣列上確定一個新的沒有儲存資料的位置;鏈地址法是在陣列的每個資料項都建立一個子連結串列用於儲存對映到相同位置的Entry<K,V>。在jdk 1.8中HashTable應用的是鏈地址法,下面主要講解一下HashTable的put方法。

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        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;
    }

以上是jdk 1.8中HashTable類中的put方法原始碼,程式通過呼叫該方法可實現鍵值對的儲存。
1、當key值為null時
首先看這一句,

if (key == null)
            return putForNullKey(value);

我們知道HashTable的key與value是可以為null的,這個if語句就是判斷當key值為null時呼叫putForNullKey(value)方法,該方法如下:

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

當key為null時預設儲存的陣列位置是table[0](table即雜湊表中用於儲存資料的陣列)。

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

該for迴圈用於判斷table[0]是否儲存有資料,如果有資料就將原來儲存在該位置的Entry<K,V>的value值替換為新的value值,返回舊的value值;如果為空就呼叫addEntry(0, null, value, 0);方法將該Entry<K,V>進行儲存,該方法程式碼如下:

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

2、當key值不為null

int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);

首先呼叫雜湊函式計算得出hash值,後一句相當於對 length 取模,也就是 hash%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;
            }
        }

同樣首先是判斷table[i]位置上是否儲存有資料,i即剛剛計算出的在陣列上的儲存位置。如果該位置上儲存有資料Entry<K,V>,則e.hash == hash && ((k = e.key) == key || key.equals(k))判斷原有的Entry的key值是否與新加入的Entry的key值相同,如果相同則進行value值得替換,返回舊的value值

V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;

如果key值首次比較不重複,那麼就在該連結串列上進行第二次、第三次的比較,直到Entry為空,此時跳出迴圈,將此Entry連結在連結串列得最後並返回null

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