JDK 版本: 1.8.0_45
常量
- DEFAULT_INITIAL_CAPACITY = 16; 默認容器大小 2的整數(shù)冪
- MAXIMUM_CAPACITY = 1<<30; 最大容器大小 2的整數(shù)冪
- DEFAULT_LOAD_FACTOR = 0.75f; 負載因子, 當容器中的數(shù)據(jù)占用容器達到 75% 時, 進行擴容
- TREEIFY_THRESHOLD = 8; 當沖突的數(shù)據(jù)量到達這個值時, 轉(zhuǎn)成二叉樹存儲
- UNTREEIFY_THRESHOLD = 6; resize 之后, 如果沖突的項小于這個值, 則轉(zhuǎn)成鏈表形式
- MIN_TREEIFY_CAPACITY = 64; 如果容器大小小于這個值, 則不會使用二叉樹, 而是先擴容,
主要成員變量
- table: 存放鍵值對的數(shù)組, 大小是2的整數(shù)次冪, 如果數(shù)據(jù)量超過了負載因子, 會自動擴容
- entrySet: 用于 keySet() 和 values()
- size: 鍵值對的數(shù)量
- modCount: 操作次數(shù)(增加/刪除鍵值對), 處理集合全部元素時, 如果有其他線程操作集合則報錯
- threshold: 下一次擴容的鍵值對數(shù)量, (即當前容量*負載因子)
主要方法
-
put(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
put(...) 方法最終都調(diào)用這個方法, 操作如下:
- 獲取容器(table)大小, 如果容器未初始化, 則調(diào)用(
resize()
)初始化 - 如果table中(
hash&(n-1)
)值所在的桶是空, 則table[hash&(n-1)]
直接指向當前對象(結束) - 否則, 如果找到 key 相同的鍵值對, 則直接將鍵值對的值設置為當前值, 返回舊值
- 如果當前對象是一個 TreeNode, 則直接插入到這個二叉樹中, 如果值已存在, 替換并返回舊值, 否則插入到尾部返回 null
- 如果當前是鏈表, 則遍歷, 有相同的key就替換, 沒有就插入到尾部, 如果鏈表長度大于等于 TREEIFY_THRESHOLD 則轉(zhuǎn)成二叉樹(TreeNode)
- 獲取容器(table)大小, 如果容器未初始化, 則調(diào)用(
-
removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)
- 如果table 為空, 或者table[hash&(n-1)] 為空, 直接返回 null (結束)
- 查找 key 值相同的對象(從鏈表或者TreeNode中查找)
- 查找到, 則再匹配 value, 如果有需要的話, 匹配成功則刪除這個鍵值對
數(shù)據(jù)結構
Paste_Image.png
TreeNode 是一個紅黑樹, 查找刪除的復雜度都是 Log(n), 上圖的樹畫的不對
總結
- HashMap 全部方法都沒有進行同步處理的, 因此盡量不要在多線程中使用
- Java8 的 HashMap 跟以前的版本不一樣了, 代碼有了更多的優(yōu)化
- 現(xiàn)在的版本使用二叉樹(TreeNode)解決 key 沖突時的查找效率問題
- 現(xiàn)在的要求容器大小必須是2的冪次, 計算鍵值對所在的桶因此頁不需要使用取余符號了(位運算效率高)
- 以前的版本為了解決某個 key 沖突比較多, 在 rehash() 時會將容器擴大1倍+1, 這樣原來沖突的key現(xiàn)在極有可能不沖突了. 而現(xiàn)在的版本, 當容器擴大時, 原來沖突的現(xiàn)在很有可能繼續(xù)沖突, 使用二叉樹優(yōu)化, 可以達到更好的效果