1. 程式人生 > >深入理解HashMap及面試相關問答

深入理解HashMap及面試相關問答

前言

HashMap是面試必備的一個知識點,無論你是初級中級還是高階,基本上逃不過這個問題,下面的內容很簡單,只要你理解了其中的含義,這對你使用hashmap和麵試都是很有幫助的。

正文

首先開啟HashMap,看看中都定義了哪些成員變數。

paramInMap

解釋幾個重點的變數

transient int size:記錄了Map中KV對的個數

loadFactor 裝載因子,用來衡量HashMap滿的程度。loadFactor的預設值為0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)

int threshold; 臨界值,當實際KV個數超過threshold時,HashMap會將容量擴容,threshold=容量(capacity)*載入因子(loadFactor)

一個重要的概念,容器的容量,capacity:DEFAULT_INITIAL_CAPACITY= 1 << 4; // aka 16

size 和 capacity

HashMap中的size和capacity之間的區別其實解釋起來也挺簡單的。我們知道,HashMap就像一個“桶”,那麼capacity就是這個桶“當前”最多可以裝多少元素,而size表示這個桶已經裝了多少元素。當達到擴容條件的時候,就需要進行擴容了,會從16擴容成32,依次都是2的冪。如下程式碼

 @Test
    public  void  test19() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Map<String, String> map = new HashMap<String, String>();
        map.put("key", "value");
        Class<?> mapType = map.getClass();
        Method capacity = mapType.getDeclaredMethod("capacity");
        capacity.setAccessible(true);
        System.out.println("capacity : " + capacity.invoke(map));

        Field size = mapType.getDeclaredField("size");
        size.setAccessible(true);
        System.out.println("size : " + size.get(map));
}

 

 

 

 

我們定義了一個新的HashMap,並向其中put了一個元素,然後通過反射的方式列印capacity和size。輸出結果為:capacity : 16、size : 1 

一個HashMap預設的容量(capacity)是16,原因是可以使用按位與替代取模來提升hash的效率。 

下面我來通過構造方法指定容量大小,大家在看一看這個“桶”的實際大小是多少。

@Test
    public  void  test19() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Map<String, String> map = new HashMap<String, String>(7);
        map.put("key", "value");
        Class<?> mapType = map.getClass();
        Method capacity = mapType.getDeclaredMethod("capacity");
                capacity.setAccessible(true);
        System.out.println("capacity : " + capacity.invoke(map));
}

分別把7改成10 和63,然後執行

控制檯分別輸出capacity : 8、capacity : 16、capacity : 64。

我來解釋一下為什麼是這三個容量

通過建構函式指定了一個數字作為容量,那麼Hash會選擇大於該數字的第一個2的冪作為容量

 

 

Map<String, String> map = new HashMap<String, String>(7);

就是最接近7以上還有誰是2的冪,肯定是8啊,2*2*2=8 ,假如是63就是capacity=64,因為64是63 下一個2次方的數。所以啊,如果各位同學想指定這個“桶”的大小時最好你就直接指定2的次方數,免得你還的算。

下面講HashMap擴容機制

首先就是時候擴容,擴容的條件是什麼

 

擴容條件就是當HashMap中的元素個數(size)超過臨界值(threshold)時就會自動擴容。

threshold(臨界值) = loadFactor(裝載因子預設值為0.75f) * capacity(容量)。

預設情況下,當其size大於12(16*0.75)時就會觸發擴容。

Map<String, String> map = new HashMap<String, String>(7,0.7f);

HashMap中還提供了一個支援傳入initialCapacity,loadFactor兩個引數的方法,來初始化容量和裝載因子。不過,一般不建議修改loadFactor的值

下面給讀者一些死記硬背的套話,面試基本上是這個問答套路

1.Hashtable是執行緒安全的,它的每個方法中都加入了Synchronize方法

2.HashMap是繼承自AbstractMap類,而HashTable是繼承自Dictionary類。不過它們都實現了同時實現了map、Cloneable(可複製)、Serializable(可序列化)這三個介面

3.當需要多執行緒操作的時候可以使用執行緒安全的ConcurrentHashMap。ConcurrentHashMap雖然也是執行緒安全的,但是它的效率比Hashtable要高好多倍。因為ConcurrentHashMap使用了分段鎖,並不對整個資料進行鎖定

4.HashMap的get過程是先得到key的hash值,再把這個hash值與length-1按位與(取餘),得到table陣列的下標。取出這個下標值的key,與傳入的key比較,如果相同那就是這個了。如果不同呢,那就沿著這個單向連結串列向後找,直到找到或找到結束也找不到。這裡的length是有特點的,是2的n次方。

5.HashMap擴容機制是在put時,容量不夠用的時候。因為每個元素都是一個單向連結串列,所以map裡放的實際數量總是大於等於申請的空間。

6.HashMap可以接受null鍵值和值,而Hashtable則不能。

7.HashMap是非synchronized所以HashMap很快。

8.hashcode相同,所以兩個物件是相等的,HashMap將會丟擲異常,或者不會儲存它們。然後面試官可能會提醒他們有equals()和hashCode()兩個方法,並告訴他們兩個物件就算hashcode相同,但是它們可能並不相等。一些面試者可能就此放棄,而另外一些還能繼續挺進,他們回答“因為hashcode相同,所以它們的bucket位置相同,‘碰撞’會發生。因為HashMap使用連結串列儲存物件,這個Entry(包含有鍵值對的Map.Entry物件)會儲存在連結串列中。”這個答案非常的合理,雖然有很多種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法
9.當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會建立原來HashMap大小的兩倍的bucket陣列,來重新調整map的大小,並將原來的物件放入新的bucket陣列中。這個過程叫作rehashing,因為它呼叫hash方法找到新的bucket位置

10.當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個執行緒都發現HashMap需要重新調整大小了,它們會同時試著調整大小。在調整大小的過程中,儲存在連結串列中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap並不會將元素放在連結串列的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那麼就死迴圈了。這個時候,你可以質問面試官,為什麼這麼奇怪,要在多執行緒的環境下使用HashMap呢?

任何一個小小的知識點都可以無限延伸,都可以追溯到硬體、計算機底層原理。

大多數問題都在這,有些面試官非要問到你不會為止,一直追問到底,最後都問到硬體了,你就說不會吧,給他點面子,萬一答上來他會很沒面子。

注:對本文有異議或不明白的地方微信探討,wx:15524579896