文章目錄
HashMap源碼閱讀
介紹:
- 繼承自Map.Entry<K,V>
- 無序、允許爲null、非同步
結構圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XQYRSLti-1593679820875)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200702162547.png)]
分析
參數
- 默認容量16
- 最大容量2的31次方
- 默認的填充因子0.75(太大查詢效率低,太小數組利用率低)
- 當bucket上的結點大於8時會轉成紅黑樹
- 當bucket上的結點小於6時會轉換爲鏈表
- 樹化參數64,當小於64時只是簡單的擴容,大於64是會樹化
- Node:靜態內部類,用來表示鍵值對
- TreeNode:紅黑樹節點
關鍵概念
-
存儲容器
-
因爲hashmap內部是用一個數組來保存內容的,數組定義如下
transient Node<K,V>[] table;
-
-
Node類型
-
table是一個Node類型的數組,Node是其中定義的靜態內部類,主要包含hash、key、value、和next屬性
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
-
-
TreeNode
- 當桶內的鏈表達到8的時候,會將鏈表轉化爲紅黑樹,就是TreeNode類型,也就是HashMap中定義的靜態內部類
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; }
常見問題
HashMap數據結構:
哈希表結構(鏈表散列:數組+鏈表),結合數組和鏈表的有點,當鏈表長度超過8時,鏈表轉化爲紅黑樹
HashMap 工作原理
HashMap的底層是hash數組和單向鏈表實現的,數組中的每個元素都是鏈表,由Node內部類(實現Map.Entry接口)實現,HashMap通過put&get方法存儲和獲取
存儲對象的時候,將k/v傳給put()方法:
- 初始化table:判斷table是否爲空或者爲null
- 計算hash值:通過高16位和低16位的異或運算(讓高16位也參入進來以便減低衝突)
- 插入或更新節點:根據(n-1)&hash計算得到插入的數組下標i然後進行判斷
- table[i]==null:沒有hash衝突直接新建節點添加
- table[i]!=null:判斷首個節點和key是否一樣
- 相同:直接更新value
- 不相同:判斷是否是紅黑樹
- 紅黑樹:直接在樹中插入鍵值對
- 鏈表:遍歷鏈表判斷是否已經存在此key:
- 存在更新
- 不存在就插入(1.8是尾插入),然後判斷是否轉爲紅黑樹
- 擴容:根據size和數組長度*負載因子來判斷是否需要擴容
獲取對象時,將 K 傳給 get() 方法:
- 調用 hash(K) 方法(計算 K 的 hash 值)從而獲取該鍵值所在鏈表的數組下標;
- 確定桶的位置後,會出現三種情況
- 單節點類型:只要不發生哈希碰撞就是這種類型
- 鏈表類型:遍歷鏈表,知道找到key相等的Node
- 紅黑樹類型:使用紅黑樹專用的快速查找方法
hashCode 是定位的,存儲位置;equals是定性的,比較兩者是否相等。
爲什麼要一起重寫hashCode()和equal()方法
假設都不重寫
這樣hashMap就可以存在key相同的值
只重寫hashCode
無法正確地與鏈表元素進行相等判斷,從而無法保證去重
只重寫equals()方法
映射到HashMap中不同數組下標,無法保證去重
當兩個對象的hashCode相同會發生什麼
因爲hashCode相同,但是不一定就是相等的(需要根據equals方法比較),所以兩個對象所在數組的下標相同,碰撞就此發生。
hash的實現
在jdk1.8中,是通過hashCode()的高16位異或低16位實現的:(h=k.hashCode())^h(>>>16),主要從速度、功效和質量來考慮的,減少系統開銷,也不會造成因爲高位沒有參數下標的計算從而引起的碰撞
使用異或運算符
保證了對象的hashCode32位值只要有一位發生改變,整個hash()返回值就會改變,儘可能減少碰撞
數組擴容的過程
創建一個新的數組,其容量爲舊數組的兩倍,並重新計算舊數組中結點的存儲位置。
結點在新數組中的存儲位置只有兩種:原下標或者原下標+舊數組的大小
拉鍊法導致鏈表過深問題爲什麼使用紅黑樹不選擇二叉樹,爲啥不一直使用紅黑樹
因爲二叉查詢樹在特殊情況下回編程一條線性結構(造成一樣的問題),導致遍歷查詢緩慢,而紅黑樹可以解決這個問題。
紅黑樹在插入新數據後,可能需要左旋、右旋、變色這些操作來保持平衡,但是爲了保持平衡需要付出一些代價,所以在鏈表長度較小(8)時,不使用紅黑樹
紅黑樹的介紹
- 每個結點非紅即黑
- 根節點總是黑色的
- 如果節點是紅色的,則其子節點必須是黑色的
- 每個葉子節點都是黑色的空節點(NIL節點)
- 從根節點到葉子節點或空節點的每條路徑,必須包含相同數目的黑色節點
HashMap、LinkedHashMap、TreeMap有什麼區別與使用場景
HashMap:(使用最多)
- 區別:
- 使用場景:在map中插入、刪除和定位元素
LinkedHashMap:
- 區別:保存了記錄的插入順序,在用Iterator遍歷時,先取到的記錄肯定是先插入的,遍歷比HashMap慢
- 使用場景:在需要輸出的順序和輸入的順序相同的情況下
TreeMap:
- 區別:實現SortMap接口,能夠把它保存的記錄根據鍵排序(默認升序)
- 使用場景:需要按自然順序或自定義順序遍歷鍵的情況
HashMap和HashTable區別
- HashMap是線程不安全的,HashTable是線程安全的,導致效率也低下
- HashMap最多隻允許一條記錄的鍵爲null,與韓旭旭多條記錄的值爲null,而HashTable不允許
- HashMap默認初始化數組大小爲16,擴容爲兩倍、HashTable爲11,擴容爲兩倍+1
最後
- 如果覺得看完有收穫,希望能給我點個贊,這將會是我更新的最大動力,感謝各位的支持
- 歡迎各位關注我的公衆號【java冢狐】,專注於java和計算機基礎知識,保證讓你看完有所收穫,不信你打我
- 如果看完有不同的意見或者建議,歡迎多多評論一起交流。感謝各位的支持以及厚愛。