HashMap常見面試題整理

1.談一下HashMap的特性?

  • 1.HashMap存儲鍵值對實現快速存取,允許爲null。key值不可重複,若key值重複則覆蓋。
  • 2.非同步,線程不安全。
  • 3.底層是hash表,不保證有序(比如插入的順序)

 

2.談一下HashMap的底層原理是什麼?

  • 基於hashing的原理,jdk8後採用數組+鏈表+紅黑樹的數據結構。我們通過put和get存儲和獲取對象。當我們給put()方法傳遞鍵和值時,先對鍵做一個hashCode()的計算來得到它在bucket數組中的位置來存儲Entry對象。當獲取對象時,通過get獲取到bucket的位置,再通過鍵對象的equals()方法找到正確的鍵值對,然後在返回值對象。

 

3.談一下hashMap中put是如何實現的?

  • 1.計算關於key的hashcode值(與Key.hashCode的高16位做異或運算)
  • 2.如果散列表爲空時,調用resize()初始化散列表
  • 3.如果沒有發生碰撞,直接添加元素到散列表中去
  • 4.如果發生了碰撞(hashCode值相同),進行三種判斷
  •     4.1:若key地址相同或者equals後內容相同,則替換舊值
  •     4.2:如果是紅黑樹結構,就調用樹的插入方法
  •     4.3:鏈表結構,循環遍歷直到鏈表中某個節點爲空,尾插法進行插入,插入之後判斷鏈表個數是否到達變成紅黑樹的闕值8;也可以遍歷到有節點與插入元素的哈希值和內容相同,進行覆蓋。
  • 5.如果桶滿了大於閥值,則resize進行擴容

4.談一下hashMap中什麼時候需要進行擴容,擴容resize()又是如何實現的?

調用場景:

1.初始化數組table

2.當數組table的size達到闕值時即++size > load factor * capacity 時,也是在putVal函數中

實現過程:(細講)

1.通過判斷舊數組的容量是否大於0來判斷數組是否初始化過

否:進行初始化

判斷是否調用無參構造器,

是:使用默認的大小和闕值

否:使用構造函數中初始化的容量,當然這個容量是經過tableSizefor計算後的2的次冪數

是,進行擴容,擴容成兩倍(小於最大值的情況下),之後在進行將元素重新進行與運算複製到新的散列表中





概括的講:擴容需要重新分配一個新數組,新數組是老數組的2倍長,然後遍歷整個老結構,把所有的元素挨個重新hash分配到新結構中去。

PS:可見底層數據結構用到了數組,到最後會因爲容量問題都需要進行擴容操作

5.談一下hashMap中get是如何實現的?

  • 對key的hashCode進行hashing,與運算計算下標獲取bucket位置,如果在桶的首位上就可以找到就直接返回,否則在樹中找或者鏈表中遍歷找,如果有hash衝突,則利用equals方法去遍歷鏈表查找節點。

 

6.談一下HashMap中hash函數是怎麼實現的?還有哪些hash函數的實現方式?

  • 對key的hashCode做hash操作,與高16位做異或運算
  • 還有平方取中法,除留餘數法,僞隨機數法

 

7.爲什麼不直接將key作爲哈希值而是與高16位做異或運算?

  • 因爲數組位置的確定用的是與運算,僅僅最後四位有效,設計者將key的哈希值與高16爲做異或運算使得在做&運算確定數組的插入位置時,此時的低位實際是高位與低位的結合,增加了隨機性,減少了哈希碰撞的次數。

 

HashMap默認初始化長度爲16,並且每次自動擴展或者是手動初始化容量時,必須是2的冪。

 

8.爲什麼是16?爲什麼必須是2的冪?如果輸入值不是2的冪比如10會怎麼樣?

https://blog.csdn.net/sidihuo/article/details/78489820

https://blog.csdn.net/eaphyy/article/details/84386313

  • 1.爲了數據的均勻分佈,減少哈希碰撞。因爲確定數組位置是用的位運算,若數據不是2的次冪則會增加哈希碰撞的次數和浪費數組空間。(PS:其實若不考慮效率,求餘也可以就不用位運算了也不用長度必需爲2的冪次)
  • 2.輸入數據若不是2的冪,HashMap通過一通位移運算和或運算得到的肯定是2的冪次數,並且是離那個數最近的數字

 

9.談一下當兩個對象的hashCode相等時會怎麼樣?

  • 會產生哈希碰撞,若key值相同則替換舊值,不然鏈接到鏈表後面,鏈表長度超過闕值8就轉爲紅黑樹存儲

 

10.如果兩個鍵的hashcode相同,你如何獲取值對象?

  • HashCode相同,通過equals比較內容獲取值對象

11."如果HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?

  • 超過闕值會進行擴容操作,概括的講就是擴容後的數組大小是原數組的2倍,將原來的元素重新hashing放入到新的散列表中去。

12.HashMap和HashTable的區別

相同點:都是存儲key-value鍵值對的

不同點:

  • HashMap允許Key-value爲null,hashTable不允許;

  • hashMap沒有考慮同步,是線程不安全的。hashTable是線程安全的,給api套上了一層synchronized修飾;

  • HashMap繼承於AbstractMap類,hashTable繼承與Dictionary類。

  • 迭代器(Iterator)。HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException。

  • 容量的初始值和增加方式都不一樣:HashMap默認的容量大小是16;增加容量時,每次將容量變爲"原始容量x2"。Hashtable默認的容量大小是11;增加容量時,每次將容量變爲"原始容量x2 + 1";

  • 添加key-value時的hash值算法不同:HashMap添加元素時,是使用自定義的哈希算法。Hashtable沒有自定義哈希算法,而直接採用的key的hashCode()。

 

13.請解釋一下HashMap的參數loadFactor,它的作用是什麼?

  • loadFactor表示HashMap的擁擠程度,影響hash操作到同一個數組位置的概率。默認loadFactor等於0.75,當HashMap裏面容納的元素已經達到HashMap數組長度的75%時,表示HashMap太擠了,需要擴容,在HashMap的構造器中可以定製loadFactor。

 

14.傳統hashMap的缺點(爲什麼引入紅黑樹?):

  • JDK 1.8 以前 HashMap 的實現是 數組+鏈表,即使哈希函數取得再好,也很難達到元素百分百均勻分佈。當 HashMap 中有大量的元素都存放到同一個桶中時,這個桶下有一條長長的鏈表,這個時候 HashMap 就相當於一個單鏈表,假如單鏈表有 n 個元素,遍歷的時間複雜度就是 O(n),完全失去了它的優勢。針對這種情況,JDK 1.8 中引入了 紅黑樹(查找時間複雜度爲 O(logn))來優化這個問題。

 

15. 平時在使用HashMap時一般使用什麼類型的元素作爲Key?

  • 選擇Integer,String這種不可變的類型,像對String的一切操作都是新建一個String對象,對新的對象進行拼接分割等,這些類已經很規範的覆寫了hashCode()以及equals()方法。作爲不可變類天生是線程安全的,
  •  

源碼解析閱讀:

1.https://blog.csdn.net/u011240877/article/details/53358305#%E4%BC%A0%E7%BB%9F-hashmap-%E7%9A%84%E7%BC%BA%E7%82%B9

2. https://juejin.im/post/5ad40593f265da23750759ad

3. https://juejin.im/post/5afbff9451882542877353dd

4. https://blog.csdn.net/panweiwei1994/article/details/76555359#commentBox

5.

 

更多關於hashMap集合的面試題:

https://zhuanlan.zhihu.com/p/32355676

https://zhuanlan.zhihu.com/p/40760616

https://juejin.im/post/5a99544ef265da23a334ab6c

https://www.jianshu.com/p/7af5bb1b57e2

https://baiqiantao.github.io/Java/%E9%9B%86%E5%90%88/3AFbAb/

發佈了37 篇原創文章 · 獲贊 161 · 訪問量 49萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章