點贊在看,養成習慣。java
點贊收藏,人生輝煌。web
點擊關注【微信搜索公衆號:編程背鍋俠】,防止迷路。編程
HashMap
基於哈希表的Map
接⼝口實現,是以key-value存儲形式存在,即主要⽤用來存放鍵值對。它的key、value均可覺得null。HashMap
的實現不是同步的,這意味着它不是線程安全的。此外,HashMap
中的映射不是有序的,位置由hashcode
通過運算決定。數組
JDK1.8 以前 HashMap 由
數組+鏈表
組成的,數組是HashMap
的主體,鏈表則是主要爲了了解決哈希衝突**(兩個對象調用的hashCode⽅法計算的哈希碼值一致而致使計算的數組索引值相同)而存在的(「拉鍊法」解決衝突).JDK1.8 之後在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(或者紅黑樹的邊界值,默認爲 8)而且當前數組的長度大於64**時【同時知足這兩個條件】,此時此索引位置上的全部數據改成使用紅⿊樹存儲。緩存
補充:將鏈表轉換成紅黑樹前會判斷,即便閾值大於8,可是數組長度小於64,此時並不會將鏈表變爲 紅黑樹。⽽是選擇進行數組擴容。安全
這樣作的目的是由於數組比較小的時候要盡量避開紅黑樹結構,這種狀況下變爲紅黑樹結構,反而會下降效率,由於紅⿊樹須要進行左旋,右旋,變色這些操做來保持平衡 。同時數組⻓度⼩於64時,搜索時間相對要快些。因此綜上所述爲了提升性能和減小搜索時間,底層在閾值大於8而且數組長度大於64時, 鏈表才轉換爲紅黑樹。具體能夠參考 treeifyBin 方法。微信
固然雖然增了了紅黑樹做爲底層數據結構,結構變得複雜了,可是閾值大於8而且數組長度大於64時,鏈表轉換爲紅黑樹時,效率也變的更⾼效。數據結構
存取是無序的。 鍵和值都均可以是null,可是這些鍵中僅只能有一個是null。 鍵位置是惟一的,底層的數據結構控制鍵的位置。 jdk1.8以前數據結構是:鏈表 + 數組, jdk1.8以後是 : 鏈表 + 數組 + 紅黑樹。 閾值(邊界值) > 8 而且數組長度大於64,纔將鏈表轉換爲紅黑樹,變爲紅黑樹的⽬的是爲了高效的查詢。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 省略 } 複製代碼
補充:經過上述繼承關係咱們發現一個很奇怪的現象, 就是HashMap已經繼承了AbstractMap而 AbstractMap類實現了Map接⼝口,那爲什什麼HashMap還要在實現Map接⼝口呢?一樣在ArrayList中 LinkedList中都是這種結構。框架
據 Java集合框架的創始⼈人Josh Bloch描述,這樣的寫法是⼀個失誤。在java集合框架中,相似這樣的寫法不少,最開始寫Java集合框架的時候,他認爲這樣寫,在某些地方多是有價值的,直到他意識到搞錯了。顯然的,JDK的維護者,後來不認爲這個⼩小的失誤值得去修改,因此就這樣存在下來了。編輯器
private static final long serialVersionUID = 362498820763181265L;
複製代碼
// 默認的初始容量是16 -- 1<<4至關於1*2的4次方---1*16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 複製代碼
1 << 4解析,就是講1對應的二進制左移4位。0000 0001【1】左移4位爲0001 0000【16】。
// 集合最大容量的上限是:2的30次冪
static final int MAXIMUM_CAPACITY = 1 << 30; 複製代碼
static final float DEFAULT_LOAD_FACTOR = 0.75f;
複製代碼
static final int TREEIFY_THRESHOLD = 8;
複製代碼
// 當桶(bucket)上的結點數小於這個值時樹轉鏈表
static final int UNTREEIFY_THRESHOLD = 6; 複製代碼
// 桶中結構轉化爲紅黑樹對應的數組長度最小的值
static final int MIN_TREEIFY_CAPACITY = 64; 複製代碼
當Map⾥面的數量超過這個值時,表中的桶才能進行樹形化 ,不然桶內元素太多時會擴容,⽽不是樹形化。目的是爲了不進⾏擴容、樹形化選擇的衝突,這個值不能⼩於 4 * TREEIFY_THRESHOLD (8)
// 存儲元素的數組
transient Node<K,V>[] table; 複製代碼
table在JDK1.8中咱們了解到HashMap是由
數組加鏈表加紅⿊樹
來組成的結構其中table就是HashMap 中的數組,jdk8以前數組類型是Entry<K,V>類型。從jdk1.8以後是Node<K,V>類型。只是換了個名字, 都實現了同樣的接口:Map.Entry<K,V>。負責存儲鍵值對數據的。
// 存放具體元素的集合
transient Set<Map.Entry<K,V>> entrySet; 複製代碼
java語言的關鍵字,變量修飾符,若是用transient聲明一個實例變量,當對象存儲時,它的值不須要維持。換句話說,用transient關鍵字標記的成員變量不參與序列化過程。
當一個對象被序列化的時候,transient型變量的值不包括在序列化的表示中,然而非transient型的變量是被包括進去的。
// 存放元素的個數,注意這個不等於數組的長度。
transient int size; 複製代碼
size爲HashMap中K-V的實時數量,不是數組table的⻓度。
// 每次擴容和更改map結構的計數器
transient int modCount; 複製代碼
// 臨界值 當實際⼤小(容量*負載因⼦)超過臨界值時,會進行擴容
int threshold; 複製代碼
// 加載因子
final float loadFactor; 複製代碼
loadFactor加載因子,是⽤來衡量 HashMap 中元素滿的程度,表示HashMap的疏密程度,能夠影響hash操做到同一個數組位置的機率,計算HashMap的實時加載因子的方法爲:size/capacity,⽽不是佔用桶的數量去除以capacity。capacity 是桶的數量,也就是 table 的⻓度length。size是集合中實際存儲元素的個數。
loadFactor太⼤致使查找元素效率低,過小致使數組的利用率低,存放的數據會很分散。loadFactor 的默認值爲0.75f是官方給出的⼀個比較好的臨界值。
當HashMap⾥面容納的元素已經達到HashMap數組⻓度的75%時,表示HashMap太擠了,須要擴容,⽽而擴容這個過程涉及到 rehash、複製數據等操做,很是消耗性能。因此開發中盡量減小擴容的次數,能夠經過建立HashMap集合對象時指定初始容量來盡量避免。
同時在HashMap的構造器中能夠定製loadFactor。這個不經常使用。這個構造方法會在接下來的文章中講解。
創做不易, 很是歡迎你們的點贊、評論和關注(^_−)☆ 你的點贊、評論以及關注是對我最大的支持和鼓勵,而你的支持和鼓勵 我繼續創做高質量博客的動力 !!!