JDK原始碼-HashMap-put方法(JDK7和JDK8)
阿新 • • 發佈:2018-11-09
下面是對HashMap中put方法的原始碼進行註釋
測試程式碼
/**
* 測試put操作的區別
*/
@Test
public void put(){
HashMap<String, String> map = new HashMap();
map.put("aa","aa");
}
JDK 7
public V put(K key, V value) {
//校驗key是否為空
if (key == null)
return putForNullKey (value);
int hash = hash(key); //獲取key對應的hash值
int i = indexFor(hash, table.length); //得到該KV對應的table的index
//這個for迴圈就是在校驗table[i]對應的連結串列中要插入的K key有沒有存在,如果有,那麼就用put的 value替換,然後返回該key對應的老的value
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++;//修改次數+1
addEntry(hash, key, value, i); //確定key沒有重複之後,插入(K,V)
return null;
}
點選 addEntry
方法
void addEntry(int hash, K key, V value, int bucketIndex) {
//判斷是否需要擴容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0; //擴容後,對應的hash需要重新計算
bucketIndex = indexFor(hash, table.length);//擴容後,對應的bucketIndex需要重新計算
}
//判讀是否需要擴容後,插入
createEntry(hash, key, value, bucketIndex);
}
點選 createEntry
方法
void createEntry(int hash, K key, V value, int bucketIndex) {
//採用頭插法插入(由於上面已經對key是否存在校驗過了,所以這裡保證了HashMap中要插入的key是不存在的,所以這裡採用頭插法是沒有問題的)
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
JDK 8
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
點選 putVal
方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
/**
* Node<K,V>[] tab 指向HashMap中table的指標
* Node<K,V> p table[i]指向的物件
* n table的長度
* i p在table對應的index
*/
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果還沒初始化就初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果插入到bulkindex對應的table的slot還沒有值,那麼直接將(K,V)封裝的newNode物件賦給tab[i]
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果插入的key跟table[i]的key相同,那麼將table[i]的slot的值(node)賦給e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//否則,如果table[i]是紅黑樹型別,則呼叫紅黑樹的插入方法
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//否則,table[i]是連結串列
else {
for (int binCount = 0; ; ++binCount) {
//採用尾插法插入新的node
if ((e = p.next) == null) { //注意一個很隱蔽的操作,e已經賦值為p.next了。開始遍歷連結串列,e就是臨時記錄node的指標
p.next = newNode(hash, key, value, null);
//如果連結串列的長度大於指定的長度,預設是8,則轉換成紅黑樹
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果連結串列中有存在跟插入的node的key和hash是一樣的話,那麼e指定該節點,然後退出迴圈
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; //下個節點
}
}
//說明有key相同的node
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //修改次數+1
if (++size > threshold) //插入後判斷是否需要擴容
resize();
afterNodeInsertion(evict);//相容LinkedHashMap的方法,針對HashMap不用管
return null;
}
總結
JDK 7 的插入操作是先判斷key是否已經存在,
如果存在,替換value,返回old value
如果不存在,頭插法插入
JDK8 的插入先判斷是否存在,
如果存在則替換value,用一個Node e指向old node,用來後續判斷
如果是不存在,直接在尾部插入該node
如果是紅黑樹,那麼呼叫紅黑樹的put方法