Map集合
小白以爲請各位多多關照,有什麼不對的還請提出來,謝謝
底層採用哈希表(動態數組 +鏈表(或者紅黑樹))
數組的動態數組保證
鏈表到 紅黑樹的相互保證
存儲的是一個K,V對象 每一個都是 map.Entry 對象
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
初始值等於 16
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
做動態數組擴容負載因子
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
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 {
........
}
- 添加數據
當我們第一次put的時候如果沒有被初始化,就先初始化,初始化化之後拿到數組的長度值減一,與當前hash值做&運算,得到一個下標,如果當前這個下標等於空
,這時直接創建鏈表nod值就好了,
當存放的是nod的key 與hash 碰撞了,equals不等,他會遍歷val值沒有重複的就直接掛在鏈表最末尾上(我寫不太好 看人家的)
鏈接: https://www.pianshen.com/article/1198306081/. - 數組動態擴容
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果table表爲空或者長度爲0,就進行創建,即resize方法
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的hsahCode和寫入的key相等,就賦值給e,直接覆蓋value.
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果該桶位置爲紅黑樹
else if (p instanceof TreeNode)
//按照紅黑樹的方式寫入數據
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果是鏈表,則在鏈表的末尾插入數據
for (int binCount = 0; ; ++binCount) {
//到達鏈表的尾部
if ((e = p.next) == null) {
//在尾部插入新節點
p.next = newNode(hash, key, value, null);
//如果節點數量達到閾值轉化爲紅黑樹.
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果當前key和要插入的key相同,則跳出循環
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e!=null,說明key相同,則直接覆蓋value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果容量超過最大容量,則繼續擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
具體的流程可以概括一下:
- 判斷當前桶是否爲空,空的就需要初始化(resize 中會判斷是否進行初始化)。
- 根據當前 key 的 hashcode 定位到具體的桶中並判斷是否爲空,爲空表明沒有 Hash 衝突就直接在當前位置創建一個新桶即可。
- 如果當前桶有值( Hash 衝突),那麼就要比較當前桶中的 key、key 的 * * * hashcode 與寫入的 key 是否相等,相等就賦值給 e,在第 8 步的時候會統一進行賦值及返回。
- 如果當前桶爲紅黑樹,那就要按照紅黑樹的方式寫入數據。
- 如果是個鏈表,就需要將當前的 key、value 封裝成一個新節點寫入到當前桶的後面(形成鏈表)。
- 接着判斷當前鏈表的大小是否大於預設的閾值,大於時就要轉換爲紅黑樹。
- 如果在遍歷過程中找到 key 相同時直接退出遍歷。
- 如果 e != null 就相當於存在相同的 key,那就需要將值覆蓋。
- 最後判斷是否需要進行擴容。
① 如果該位置沒有數據,用該數據新生成一個節點保存新數據,返回null;
② 如果該位置有數據是一個紅黑樹,那麼執行相應的插入 / 更新操作;
③ 如果該位置有數據是一個鏈表,分兩種情況一是該鏈表沒有這個節點,另一個是該鏈表上有這個節點,注意這裏判斷的依據是key.hash是否一樣:
如果該鏈表沒有這個節點,那麼採用尾插法新增節點保存新數據,返回null;如果該鏈表已經有這個節點了,那麼找到該節點並更新新數據,返回老數據。
- get數據時
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果table已經初始化,長度大於0,且根據hash尋找table中的項也不爲空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//桶中第一個元素命中,直接返回
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果桶中不止一個節點
if ((e = first.next) != null) {
//如果是紅黑樹,就在紅黑樹中尋找
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//否則就在鏈表中尋找
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- 先判斷key hash如果爲空 返回null
- 如果直接命中直接返回,
- 要是裏面不至一個節點,判斷紅黑樹 還是鏈表
- 是紅黑樹就按照紅黑樹查找,鏈表就按照鏈表的方式查找
在resize方法中:
hashmap中的鍵值對大於閥值時或者初始化時,就調用resize方法進行擴容;
每次擴展的時候,都是擴展2倍;
擴展後Node對象的位置要麼在原位置,要麼移動到原偏移量兩倍的位置。