1. 程式人生 > >HashMap原理以及面試相關

HashMap原理以及面試相關

一、什麼是雜湊表?

hashmap內部維護這一個雜湊Entry陣列和一個線性連結串列,通過key的hashcode來儲存和查詢資料。而計算key的hashcode的函式稱為雜湊函式。其新增、查詢的操作如下:

這裡寫圖片描述
儲存結構如下:
這裡寫圖片描述
通過雜湊函式計算出實際儲存地址,在bucket中找到對應的位置進行的查詢、新增操作。

二、HashMap常見面試問題

1.HashMap的工作原理
2.HashMap的鍵和值可以為Null嗎?為什麼?
3.如果兩個物件的hashcode相同會發生什麼?
4.如何獲取hashcode相同的值物件?
5.如果HashMap大小超過負載因子(load factor:default 0.75)定義的容量,怎麼辦?
6.如果要調整HashMap的大小,會存在什麼問題?
7.哪些資料型別適合作為HashMap的鍵,為什麼?
8.可以用CocurentHashMap替代HashMap嗎?

三、原理解析

1.HashMap工作原理

HashMap基於hashing原理,通過put()和get()方法儲存和獲取物件,當我們呼叫put函式儲存鍵值對時,會先呼叫物件的hashCode()方法計算出key的hashcode,然後根據這個值找到bucket的位置來儲存值物件。當呼叫get()方法獲取物件的時候,也是先根據key來計算hashcode,然後找到bucket的位置來查詢對應的值物件。

2.對於第二個面試題,先來看HashMap的put方法:

//put
public V put(K key, V value){
 //如果key為null,呼叫putForNullKey()方法寫入null鍵的值
//這就是為什麼HashMap允許鍵為null的原因 if (key == null){ return putForNullKey(value); } 
//根據key的hashCode計算Hash值 int hash = hash(key.hashCode()); //查詢hash值在table中的索引 int i = indexFor(hash, table.length); // 如果 i 索引處的 Entry 不為 null,通過迴圈不斷遍歷連結串列查詢是否在連結串列中有相同key的Entry //這就是HashMap處理hash碰撞的方法,用一個連結串列來解決 for (Entry<K,V> e = tablei; e != null
; e = e.next) { Object k; //找到與插入的值的key和hash相同的Entry if (e.hash == hash && ((k = e.key) == key|| key.equals(k)){
 //key值相同時直接替換value值,跳出函式 V oldValue = e.value; e.value = value;
 e.recordAccess(this);
 return oldValue;
 }
 } // 如果 i 索引處的 Entry 為 null 或者key的hash值相同而key不同 ,則需要新增Entry modCount++; // 將 key、value 新增到 i 索引處 addEntry(hash, key, value, i); return null; } //get public V get(Object key) { // 如果key是null呼叫 getForNullKey取出null的value if (key == null) return getForNullKey(); // 根據該key的hashCode值計算它的hash碼 int hash = hash(key.hashCode()); // 直接取出table陣列中指定索引處的值, for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; // 搜尋該Entry鏈的下一個物件 e = e.next) { Object k; // 如果該Entry的key和hash與被搜尋key相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }

3.如果兩個物件的hashCode相同

儲存時:它們會找到相同的bucket位置,發生hash碰撞,對每個bucket位置,會有一個維護連結串列,通過一個next指標指向下一個Entry節點,然後將值儲存在這個連結串列中的next節點。
獲取時,會用hashCode找到這個bucket位置,然後遍歷這個連結串列,呼叫key.equal()方法,找到連結串列中正確的節點,然後返回要找的鍵值對物件。

4.HashMap擴容

HashMap的預設負載因子為0.75,這個負載因子就是用來計算當前的HashMap的容量,當map被填滿了75%bucket的時候,將會呼叫resize()方法,建立原來HashMap兩倍大小的bucket陣列,並將原來的物件放入新的陣列中,這個過程叫rehashing。

5.HashMap鍵的選擇

建議使用String、Integer作為鍵,因為我們可以使用final修飾作為不可變物件,能夠很好的防止hashCode重複導致發生hash碰撞,這樣可以提高HashMap的效能,因為不用去遍歷連結串列去查詢元素。

6.ConcurrentHashMap

ConcurrentHashMap可以替換HashMap。要根據使用場景來選擇,如果考慮多執行緒的問題,使用ConcurrentHashMap比較好,因為它僅僅根據同步級別對map的一部分進行上鎖,效率要高一些。但是在不考慮多執行緒的應用場景中,HashMap會更好。

四、總結以及其他注意點

  • 選用String、Integer作為鍵會提高HashMap的效率。
  • 如果HashMap的大小超過負載因子定義的容量,會建立一個原來兩倍大小的新bucket陣列,將原來的物件放入新陣列,這個過程影響效率且容易引發執行緒不安全問題,因為多個執行緒同時發現需要調整容量時,會出現條件競爭的現象,因為可能形成一個環形的連結串列,導致所有的next指向都不為null,進入死迴圈。
  • 多執行緒同時進行put操作也可能導致資料丟失。