1. 程式人生 > 其它 >HashMap底層儲存原理

HashMap底層儲存原理

概念

  • HasnMap是基於map介面實現,元素以鍵值對的方式儲存,並且鍵和值都可以使用null,因為 key不允許重複,因此只能有一個鍵為null
  • HaasnMap是 無序不重複的,而且HashMap是執行緒不安全 的
  • JDK7HashMap的資料結構為:陣列+連結串列
  • JDK8HashMap的資料結構為:陣列 + 連結串列 + 紅黑樹

儲存的優點

  • 陣列的特點:查詢效率高,插入和刪除效率低
  • 連結串列的特點:查詢效率 低,插入和刪除效率高
  • 在HasnMap底層使用陣列加 (連結串列或紅黑樹) 的結構完美的解決了陣列和連結串列的問題,使的查詢和插入,刪除的效率都 很高
  • HashMap的散列表是懶載入機制,在第一次put的時候才會建立

HashMap儲存元素的過程

首先將k、v封裝到Node物件當中(節點)

呼叫k的hasnCode()方法取出hash值;通過hashcode值和陣列長度取模得到元素儲存的下標

此時分為兩種情況

  • 下標位置上沒有元素,直接把元素方進入
  • 該所以已有元素,判斷該位置的元素和當前元素是否相等,使用equals來比較(預設是比較兩個物件的地址)。如果兩隻相等則直接覆蓋,如果不等則(Hash碰撞)在原元素下面使用連結串列的結構儲存該元素(如果已存在連結串列,則插在連結串列尾部),每個元素節點都有一個next屬性指向下一個節點,這就由陣列結構變成了陣列+連結串列;因為連結串列中元素太多的時候回影響查詢效率,所以當連結串列的元素個數達到 8 的時候使用連結串列儲存就轉變成了使用紅黑樹儲存(當紅黑樹上的節點數量小於 6 個,會重新把紅黑樹變成單向連結串列資料結構),原因就是紅黑樹是平衡二叉樹,在查詢效能方面比聊表要高

HashMap取值的實現

  • 先呼叫k的hashCode()方法得出雜湊值,並通過hash演算法轉換成陣列的下標
  • 通過hash值轉換成陣列下標後,通過陣列定位到下標位置,如果改位置上什麼都沒有,範圍null;如果該位置上有單向連結串列,那麼就拿引數K和單向連結串列上的每一個節點的K進行equals比較,如果所有equals都返回false,則返回null,如果有一個節點的K和引數K通過equals返回true,那麼此時該節點的value就是要獲取的value值

擴容

  • HashMap中有兩個重要引數,初始容量大小和負載因子,在HashMap剛開始初始化的時候,使用預設的構造方法,會返回一個空的table,並且 thershold(擴容閾值)為 0 ,因此第一次擴容的時候預設值就會是 16 ,負載因子預設為 0.75 ,用陣列容量乘以負載因子得到一個值,一旦陣列中儲存的元素個數超過這個值就會呼叫rehash方法將陣列容量增加到原來的兩倍,threshold也會變為原來的兩倍
  • 在做擴容的時候會生成一個新的陣列,原來的所有資料需要重新計算雜湊碼值重新分配到新的陣列,所以擴容的操作非常消耗效能。所以,如果知道要存入的資料量比較大的話,可以在建立的時候先指定一個比較大的資料容量
  • 也可以引申到一個問題HashMap是先插入還是先擴容:HashMap初始化後首次插入資料時,先發生resize擴容再插入資料,之後每當插入的資料個數達到threshold時就會發生resize,此時是先插入資料再resize

HashMap中的擴容是在元素插入之前進行的擴容還是元素插入之後進行的擴容

在 JDK1.7中是在元素插入 前 進行的擴容,在JDK1.8 中是先加入元素 後 再判斷是否進行擴容

儲存元素超過閾值一定會進行擴容嗎

在 JDK1.7 中不一定,只有儲存元素超過閾值並且當前儲存位置不為null,才會進行擴容,在 JDK1.8 中會進行擴容

HashMap和HashTable區別

執行緒方面

  • HashMap是非執行緒安全的,HashTable是執行緒安全的。 Hashtable的實現方法裡面都添加了synchronized關鍵字來確保執行緒同步,因此相對而言HashMap效能會高一些,我們平時使用時若無特殊需求建議使用HashMap,在多執行緒環境下若使用HashMap需要使用Collections.synchronizedMap()方法來獲取一個執行緒安全的集合
  • HashMap的key可以為null,HashTable的key不可為null
  • HashMap是對Map介面的實現,HashTable實現了Map介面和Dictionary抽象類
  • HashMap的初始容量為 16 ,Hashtable初始容量為 11 ,兩者的填充因子預設都是 0.75 ,HashMap擴容時是當前容量翻倍即:capacity * 2,- Hashtable擴容時是容量翻倍+1即:capacity * 2+1

HashMap中的hashcode怎麼生成

呼叫物件key的hashCode方法,再對這個hashcode方法進行一些右移以及異或運算(使的hashCode的高位和低位都參與到運算中);通過右移和異或運算可以使hashMap的雜湊化更強,提高hashMap的get方法的效率

為什麼使用HashCode

HashCode的存在主要是為了查詢的快捷性, HashCode是用來在雜湊儲存結構中確定物件的儲存地址的 ( 用hashcode來代表物件在hash表中的位置 ) , hashCode存在的重要的原因之一就是在HashMap(HashSet其實就是HashMap)中使用(其實Object類的hashCode方法註釋已經說明了),HashMap之所以速度 快 ,因為他使用的是 散列表 ,根據key的hashcode值生成陣列下標(通過記憶體地址直接查詢,不需要判斷,但是需要多出很多記憶體,相當於以空間換時間)

equals方法和hashcode的關係

歸納總結:

  • 若重寫了equals(Object obj)方法,則有必要重寫hashCode()方法
  • 若兩個物件equals(Object obj)返回true,則hashCode()有必要也返回相同的int數
  • 若兩個物件equals(Object obj)返回false,則hashCode()不一定返回不同的int數
  • 若兩個物件hashCode()返回相同int數,則equals(Object obj)不一定返回true
  • 若兩個物件hashCode()返回不同int數,則equals(Object obj)一定返回false
  • 同一物件在執行期間若已經儲存在集合中,則不能修改影響hashCode值的相關資訊,否則會導致記憶體洩露問題

key為null怎麼辦

key為null的時候,只會放在hashMap的0位置(即key的hashCode為0,對陣列長度取餘後的下標也是0),不會有連結串列 在HashMap原始碼中對put方法對null做了處理,key為null的判斷後進入putForNullKey(V value)這個方法,李裡面for迴圈是在talbe[0]連結串列 中查詢key為null的元素,如果找到,則將value重新賦值給這個元素的value,並返回原來的value。如果沒找到則將這個元素新增到talbe[0]連結串列的表頭

/** 
 * HashMap的put方法 
 */ 
public V put(K key, V value) { 
    if (table == EMPTY_TABLE) { 
        inflateTable(threshold); 
    } 
 
    // key為null呼叫putForNullKey(value) 
    if (key == null) return putForNullKey(value); 
 
    int hash = hash(key); 
    int i = indexFor(hash, table.length); 
    for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
        Object k; 
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
            V oldValue = e.value; 
            e.value = value; 
            e.recordAccess(this); 
            return oldValue; 
        } 
    } 
    modCount++; 
    addEntry(hash, key, value, i); 
    return null; 
} 
 
/** 
 * Offloaded version of put for null keys 
 */ 
private V putForNullKey(V value) { 
    // for迴圈處理key為空的情況 
    for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
        if (e.key == null) { 
            V oldValue = e.value; 
            e.value = value; 
            e.recordAccess(this); 
            return oldValue; 
        } 
    } 
    modCount++; 
    addEntry(0, null, value, 0); 
    return null; 
} 

轉載連結:作為Java開發,知道HashMap底層儲存原理總不會害你 - 51CTO.COM