別怕,遇到Map源碼面試題這樣答就對了

衆所周知,在面試大廠的過程中,HashMap源碼是必問的,當然有些還會問TreeMap 和 LinkedHashMap。如果事先沒有準備,當然會瞬間懵逼,因此在面試前建議大家抽出時間準備準備,很容易就能續寫自己的高光時刻。
高光時刻
在開始之前,強烈推薦閱讀這個多達兩萬字的HashMap源碼分析文章,是的,你想要的,它都有!強化基礎,HashMap源碼全角度詳細解析
別的廢話就不多說了,下面開始見招拆招。

1、說一說 HashMap 底層數據結構

HashMap 底層是數組 + 鏈表 + 紅黑樹的數據結構,數組的主要作用是方便快速查找,時間複雜度是 O(1),默認大小是16,數組的下標索引是通過 key 的 hashcode 計算出來的,數組元素叫做 Node,當多個 key 的 hashcode 一致,但 key 值不同時,單個 Node 就會轉化成鏈表,鏈表的查詢複雜度是 O(n),當鏈表的長度大於等於 8 並且數組的大小超過 64 時,鏈表就會轉化成紅黑樹,紅黑樹的查詢複雜度是 O(log(n)),簡單來說,最壞的查詢次數相當於紅黑樹的最大深度。

2、那你說一下Map的hash算法

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
可以得出key在數組中的位置公式:tab[(n - 1) & hash]

如上代碼是 HashMap 的hash 算法。

這其實可以理解爲一個數學問題,源碼中就是通過以上代碼來計算 hash 的,首先計算出 key 的 hashcode,因爲 key 是 Object,所以會根據 key 的不同類型進行 hashcode 的計算,接着計算h ^ (h >>> 16) ,這麼做的好處是使大多數場景下,算出來的 hash 值比較分散。

一般來說,hash 值算出來之後,要計算當前 key 在數組中的索引下標位置時,可以採用取模的方式,就是索引下標位置 = hash 值 % 數組大小,這樣做的好處,就是可以保證計算出來的索引下標值可以均勻的分佈在數組的各個索引位置上,但取模操作對於處理器的計算是比較慢的,數學上有個公式,當 b 是 2 的冪次方時,a % b = a &(b-1),所以此處索引位置的計算公式我們可以更換爲: (n-1) & hash。

當你順利的答出了上面這個問題後,可能面試官會接着追問如下四個問題,別怕,接着說就好了!

1. 爲什麼不用 key對數組大小取餘,而是需要用 key 的 hash 值對數組大小取餘?

如果 key 是數字,直接用 key % 數組大小是完全沒有問題的,但我們的 key 還有可能是字符串,是複雜對象,這時候用 字符串或複雜對象 % 數組大小是不行的,所以需要先計算出 key 的 hash 值。

2. 計算 hash 值時,爲什麼需要右移 16 位?

hash 算法是 h ^ (h >>> 16),爲了使計算出的 hash 值更分散,所以選擇先將 h 無符號右移 16 位,然後再於 h 異或時,就能達到 h 的高 16 位和低 16 位都能參與計算,減少了碰撞的可能性。

3. 爲什麼把取模操作換成了 & 操作?

key.hashCode() 算出來的 hash 值還不是數組的索引下標,爲了隨機的計算出索引的下表位置,我們還會用 hash 值和數組大小進行取模,這樣子計算出來的索引下標比較均勻分佈。

取模操作處理器計算比較慢,處理器對 & 操作就比較擅長,換成了 & 操作,是有數學上證明的支撐,爲了提高了處理器處理的速度。

4. 爲什麼提倡數組大小是 2 的冪次方?

因爲只有大小是 2 的冪次方時,才能使 hash 值 % n(數組大小) == (n-1) & hash 公式成立。

3、HashMap、TreeMap、LinkedHashMap有什麼相同點和不同點?

3.1、首先是相同點:

三者在特定的情況下,都會使用紅黑樹;
底層的 hash 算法相同;
在迭代的過程中,如果 Map 的數據結構被改動,都會報 ConcurrentModificationException 的錯誤。

3.2、其次是不同點:

HashMap數據結構以數組爲主,查詢非常快,TreeMap 數據結構以紅黑樹爲主,利用了紅黑樹左小右大的特點,可以實現 key 的排序,LinkedHashMap 在 HashMap 的基礎上增加了鏈表的結構,實現了插入順序訪問和最少訪問刪除兩種策略;
由於三種 Map 底層數據結構的差別,導致了三者的使用場景的不同:

  • TreeMap 適合需要根據 key 進行排序的場景;
  • LinkedHashMap 適合按照插入順序訪問,或需要刪除最少訪問元素的場景;
  • 剩餘場景我們使用 HashMap 即可,我們工作中大部分場景基本都在使用 HashMap。

4、HashMap源碼裏面是怎麼解決hash衝突的?

hash 衝突指的是 key 值的 hashcode 計算相同,但 key 值不同的情況。
通過學習源碼,可以知道如果桶中元素原本只有一個或已經是鏈表了,新增元素直接追加到鏈表尾部;如果桶中元素已經是鏈表,並且鏈表個數大於等於 8 時,此時有兩種情況:

  • 如果此時數組大小小於 64,數組再次擴容,鏈表不會轉化成紅黑樹;
  • 如果數組大小大於 64 時,鏈表就會轉化成紅黑樹。

這裏不僅僅判斷鏈表個數大於等於 8,還判斷了數組大小,數組容量小於 64 沒有立即轉化的原因,猜測主要是因爲紅黑樹佔用的空間比鏈表大很多,轉化也比較耗時,所以數組容量小的情況下衝突嚴重,可以先嚐試擴容,通過擴容看看能不能解決根本的問題。

5、那你說說HashMap是怎麼擴容的?

擴容需要滿足一定的條件,主要是兩種:

  1. put 時,發現數組爲空,進行初始化擴容,默認擴容大小爲 16;
  2. put 成功後,發現現有數組大小大於擴容的門閥值時,進行擴容,擴容爲老數組大小的 2 倍。

其次擴容的門閥是 threshold,每次擴容時 threshold 都會被重新計算,門閥值等於數組的大小 * 影響因子(0.75)。新數組初始化之後,需要將老數組的值拷貝到新數組上,鏈表和紅黑樹都有自己拷貝的方法。

6、那爲什麼鏈表個數大於等於8時,鏈表要轉化成紅黑樹了?爲什麼不是6,不是10?

HashMap在源碼的類註釋裏對爲什麼是8有過解釋,是這樣說的:通過泊松分佈公式計算,正常情況下,鏈表個數出現 8 的概念不到千萬分之一,所以說正常情況下,鏈表都不會轉化成紅黑樹,這樣設計的目的,是爲了防止非正常情況下,比如 hash 算法出了問題時,導致鏈表個數輕易大於等於 8 時,仍然能夠快速遍歷。
雖然成紅黑樹,可以使遍歷的時間複雜度降低,但是也要考慮到轉化成紅黑樹,有空間和轉化耗時的成本,因此不能輕易的轉化。

7、紅黑樹什麼時候轉變成鏈表?

當節點的個數小於等於 6 時,紅黑樹會自動轉化成鏈表,主要還是考慮紅黑樹的空間成本問題,當節點個數小於等於 6 時,遍歷鏈表也很快,所以紅黑樹會重新變成鏈表。

8、HashMap 在 put 時,如果數組中已經有了這個 key,我不想把 value 覆蓋怎麼辦?取值時,如果得到的 value 是空時,想返回默認值怎麼辦?

如果數組有了 key,但不想覆蓋 value ,可以選擇 putIfAbsent 方法,這個方法有個內置變量onlyIfAbsent,內置是 true ,就不會覆蓋,我們平時使用的 put 方法,內置 onlyIfAbsent 爲 false,是允許覆蓋的。
取值時,如果爲空,想返回默認值,可以使用 getOrDefault 方法,方法第一參數爲 key,第二個參數爲你想返回的默認值,如 map.getOrDefault(“2”,“0”),當 map 中沒有 key 爲 2 的值時,會默認返回 0,而不是空。

9、刪除數據時可以使用foreach進行刪除嗎?

不可以,因爲在for循環之前modCount會賦值給mc,但是循環之後,mouCount發生了變化,但是mc卻是變化之前的值,這點和ArrayList刪除數據類似,都是不可以用簡單的foreach刪除的。

10、LinkedHashMap中的LRU是怎麼實現的?

首先LRU的英文全稱是Least recently used,翻譯過來就是最近最少訪問,在 LinkedHashMap 中,也叫做最少訪問刪除策略,我們可以通過 removeEldestEntry 方法設定一定的策略,使最少被訪問的元素,在適當的時機被刪除,原理是在 put 方法執行的最後,LinkedHashMap 會去檢查這種策略,如果滿足策略,就刪除頭節點。

保證頭節點就是最少訪問的元素的原理是:LinkedHashMap 在 get 的時候,都會把當前訪問的節點,移動到鏈表的尾部,慢慢的,就會使頭部的節點都是最少被訪問的元素了。

11、爲什麼使用TreeMap時推薦把key實現 Comparable 接口?什麼情況下不需要去實現這個接口呢?

這是因爲 TreeMap 的底層就是通過排序來比較兩個 key 的大小的,所以推薦 key 實現 Comparable 接口,是爲了往你希望的排序順序上發展。
如果我們的key的類型本身已經實現了 Comparable 接口,比如String以及一些包裝類型如Long、Double、Short 等等就不需要我們再去實現 Comparable 接口了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章