hashmap之put方法詳解
存入鍵值對put:
參數:鍵key、值value
返回值value:
- 如果put時map裏已經存在同一個key,返回put之前的key所對應的value;
- 如果put時map裏不存在同一個key,返回一個null。
作用:存入鍵值對
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
調用的是putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)
hash
參數:無
返回值:一個int類型的整數,稱爲hash值。注意不是hash碼,hashCode()計算的纔是hash碼
作用:計算對象的hash值
static final int hash(Object key) {
int h;
//key是空就返回0,不空就返回hashcode異或hashcode右移16位的結果。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal
參數:key的hash值、key、value、onlyIfAbsent:true表示不能修改任意結點、evict:false表示存放結點的數組table在創建模式。
返回值:鍵值對插入的那個位置之前的位置結點的value
作用:插入鍵值對。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; //用於指向table
Node<K,V> p; //用於指向數組table的索引處的結點
int n, i;//n表示table的長度
如果table是未初始化或者table的長度是0,就重新初始化table,長度是16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
如果索引處的結點是空,就直接在此處新創建一個結點存入相關數據。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
否則
else {
Node<K,V> e; //用來表示鍵值對應該存放的位置的結點
K k;//用來表示鍵值對應該存放的位置的結點的key
如果p是鍵值對應該存放的位置的結點,把p賦值給e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
否則判斷p是不是在紅黑樹裏,如果在就去樹裏找鍵值對應該存放的位置的結點並賦值給e
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
如果不在,就遍歷鏈表
for (int binCount = 0; ; ++binCount) {
如果p的下一個結點是null,則p的下一個結點就是e,在此處新建結點,並且判斷新建結點後鏈表的長度是不是大於8,是就把這個鏈表轉成紅黑樹,結束
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
如果e是鍵值對應該存放的位置的結點,就找到了,終止循環
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
e是鍵值對應該存放的位置的結點,所以只替換e的value就達到目的了,返回替換前e的value,結束函數。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//新建了結點,把map結構改變計數器+1
++modCount;
//++size是新的size,新的size要是比計算的閾值大就擴容數組,表明快滿了
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize
參數:無
返回值:數組
作用:擴充數組。
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;原來的數組
int oldCap = (oldTab == null) ? 0 : oldTab.length;原來的數組長度
int oldThr = threshold;原來的閾值
int newCap, newThr = 0;新的數組長度、新的數組閾值
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;如果原來的數組長度已經超過了數組規定 的最大長度,返回原來的數組,結束擴容,因爲不能擴容了。
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
如果原來的數組長度已經超過了默認數組長度16,但是新數組長度小於最大長度,把
閾值擴大2倍。
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;如果舊容量是0,舊閾值>0,那嘛新容量設置成舊閾值。
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;如果舊容量是0,舊閾值也是0,那麼把新容量設置成默認的容量,把新閾值設置成默認加載因子
*默認容量。
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
設置新閾值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
hashmap的閾值等於新閾值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
根據新容量創建新數組
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
放入新的數組時會分爲樹中和鏈表,如果原先的就是鏈表,則會對原鏈表的數據進行重新排序。如果原先是紅黑樹,則會將舊樹中的node分離。
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)如果舊數組的結點是單獨的結點,直接計算索引,放入新數組
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)否則、如果舊數組的結點在紅黑樹裏,就調整紅黑樹結構
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order否則,如果舊數組的結點在鏈表裏
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
總結
hashmap的put函數過程:1.調用hash;2.調用putVal.
首先根據hash()函數計算出key的hash值,【注意不是hash碼,hashCode()計算的纔是hash碼】,然後把這個hash值和key、value、onlyIfAbsent、evict一共五個參數傳遞到putVal函數。
onlyIfAbsent:是true時表示不能修改hashmap裏的任意結點,顯然這裏應該傳入false,因爲往hashmap裏面put存鍵值對是要修改結點的【兩種情況:1.覆蓋原來的結點 2.新增一個結點】
evict:是false代表table正在構建,也是不能操作table,因爲要操作table,這裏應該傳入true。
然後,在putVal裏面:
- 判斷數組table是不是沒有初始化或數組的長度是0,滿足任意一個條件,就初始化數組。
初始化時調用resize()方法,數組長度設置爲16
-
根據hash值計算索引,索引處的結點記爲p,
如果p是null,那就沒有hash衝突,直接在此處調用newNode方法創建一個新結點。否則,
- 判斷p是不是key應該存入的位置的結點,如果是,暫時把e=p,e表示key應該存入的位置的結點,如果不是進行下一步
- 判斷p是不是在紅黑樹裏,如果是調用putTreeVal把key與value存入樹,並返回key應該存入的位置的結點給e,否則進行下一步
- 此時說明p在鏈表裏,那麼遍歷鏈表,e=p.next,如果e是空,直接新建一個結點存入數據,之後再判斷此時的鏈表長度是不是大於8,是就轉換成紅黑樹,結束遍歷。
- 如果此時的e真的是key應該存入的位置的結點,那就不用往下找了,直接結束遍歷;
如果e不是null,把e的value替換成要存入的value即可。然後結束putVal函數。
修改hashmap的結構改變次數計數器。
++size是新的size,新的size要是比計算的閾值大就擴容數組,表明快滿了,就對數組擴容。