HashMap常問面試題整理


去面試時,hashmap總是被經常問的問題,下面總結了幾道關於hashmap的問題。

1、hashmap的主要參數都有哪些?

2、hashmap的數據結構是什么樣子的?自己如何實現一個hashmap?

3、hash計算規則是什么?

4、說說hashmap的存取過程?

5、說說hashmap如何處理碰撞的,或者說說它的擴容?

解答:以1.7為例,也會摻雜一些1.8的不同點。

1、

1)桶(capacity)容量,即數組長度:DEFAULT_INITIAL_CAPACITY=1<<4;默認值為16

  即在不提供有參構造的時候,聲明的hashmap的桶容量;

2)MAXIMUM_CAPACITY = 1 << 30;

  極限容量,表示hashmap能承受的最大桶容量為2的30次方,超過這個容量將不再擴容,讓hash碰撞起來吧!

3)static final float DEFAULT_LOAD_FACTOR = 0.75f;

  負載因子(loadfactor,默認0.75),負載因子有個奇特的效果,表示當當前容量大於(size/)時,將進行hashmap的擴容,擴容一般為擴容為原來的兩倍。

4)int threshold;閾值

  閾值算法為capacity*loadfactory,大致當map中entry數量大於此閾值時進行擴容(1.8)

5)transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;(默認為空{})

  核心的數據結構,即所謂的數組+鏈表的部分。

2、hashmap的數據結構是什么樣子的?自己如何實現一個hashmap?

  主要數據結構即為數組+鏈表。

  在hashmap中的主要表現形式為一個table,類型為Entry<K,V>[] table

  首先是一個Entry型的數組,Entry為hashmap的內部類:

  

1 static class Entry<K,V> implements Map.Entry<K,V> {
2         final K key;
3         V value;
4         Entry<K,V> next;
5         int hash;
6 }

  在這里可以看到,在Entry類中存在next,所以,它又是鏈表的形式。

  這就是hashmap的主要數據結構。

3、hash的計算規則,這又要看源碼了:

  

1 static final int hash(Object key) {
2         int h;
3         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4     }

  這是1.8的源碼,1.7太復雜但原理是一致的,簡單說這就是個“擾動函數”,最終的目的是讓散列分布地更加均勻。

  算法就是拿存儲key的hashcode值先右移16位,再與hashcode值進行亦或操作,即不求進位只求按位相加的值:盜圖:

  

  最后是如何獲得,本key在table中的位置呢?本身應該是取得了hash進行磨除取余運算,但是,源碼:

1 static int indexFor(int h, int length) {
2         // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
3         return h & (length-1);
4     }

  為什么又做了個與運算求得位置呢?簡單說,它的意義和取余一致。

  不信可以自己算一下。

  首先說,他利用了table的長度肯定是2的整數次冪的原理,假設當前length為16,2的4次方

  而與&運算,又是只求進位運算,比如1111&110001結果為000001

  只求進位運算(&),保證算出的結果一定在table的length之內,最大為1111。

  故而,它的運算結果與價值等同於取余運算,並且即使不管hash值有多大都可以算出結果,並且在length之內。

  並且,這種類型的運算,能夠更加的節約計算機資源,少了加(計算機所有運算都是加運行)運算過程,更加地節省資源。

4、hashmap的存取過程

  源碼1.7:

 1 /**
 2 *往hashmap中放數據
 3 */
 4 public V put(K key, V value) {
 5         if (table == EMPTY_TABLE) {
 6             inflateTable(threshold);//判斷如果為空table,先對table進行構造
 7             //構造通過前面的幾個參數
 8         }
 9         //首先判斷key是否為null,為null也可以存
10         //這里需要記住,null的key一定放在table的0號位置
11         if (key == null)
12             return putForNullKey(value);
13         //算出key的hash值
14         int hash = hash(key);
15         //根據hash值算出在table中的位置
16         int i = indexFor(hash, table.length);
17         //放入K\V,遍歷鏈表,如果位置上存在相同key,進行替換value為新的,且將替換的舊的value返回
18         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
19             Object k;
20             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
21                 V oldValue = e.value;
22                 e.value = value;
23                 e.recordAccess(this);
24                 return oldValue;
25             }
26         }
27         modCount++;
28         //增加一個entry,有兩種情況,1、如果此位置存在entry,將此位置變為插入的entry,且將插入entry的next節點變為原來的entry;2、如果此位置不存在entry則直接插入新的entry
29         addEntry(hash, key, value, i);
30         return null;
31     }

取數據:

 1 //根據key獲得一個entry
 2 public V get(Object key) {
 3         //如果key為null,獲取0號位的切key為null的值
 4         if (key == null)
 5             return getForNullKey();
 6         //如果不是,獲取entry,在下面方法
 7         Entry<K,V> entry = getEntry(key);
 8         //合法性判斷
 9         return null == entry ? null : entry.getValue();
10     }
11 //獲取一個key不為null的entry
12 final Entry<K,V> getEntry(Object key) {
13         //如果table為null,則返回null
14         if (size == 0) {
15             return null;
16         }
17         //計算hash值
18         int hash = (key == null) ? 0 : hash(key);
19         //根據hash值獲得table的下標,遍歷鏈表,尋找key,找到則返回
20         for (Entry<K,V> e = table[indexFor(hash, table.length)];
21              e != null;
22              e = e.next) {
23             Object k;
24             if (e.hash == hash &&
25                 ((k = e.key) == key || (key != null && key.equals(k))))
26                 return e;
27         }
28         return null;
29     }

 5.擴容和碰撞

  先說碰撞吧,由於hashmap在存值的時候並不是直接使用的key的hashcode,而是通過擾動函數算出了一個新的hash值,這個計算出的hash值可以明顯的減少碰撞。

  還有一種解決碰撞的方式就是擴容,擴容其實很好理解,就是將原來桶的容量擴為原來的兩倍。這樣爭取散列的均勻,比如:

  原來桶的長度為16,hash值為1和17的entry將會都在桶的0號位上,這樣就出現了碰撞,而當桶擴容為原來的2倍時,hash值為1和17的entry分別在1和17號位上,整號岔開了碰撞。

  下面說說何時擴容,擴容都做了什么。

  1.7中,在put元素的過程中,判斷table不為空、切新增的元素的key不與原來的重合之后,進行新增一個entry的邏輯。

1 void addEntry(int hash, K key, V value, int bucketIndex) {
2         if ((size >= threshold) && (null != table[bucketIndex])) {
3             resize(2 * table.length);
4             hash = (null != key) ? hash(key) : 0;
5             bucketIndex = indexFor(hash, table.length);
6         }
7         createEntry(hash, key, value, bucketIndex);
8     }

  由源代碼可知,在新增元素時,會先判斷:

  1)當前的entry數量是否大於或者等於閾值(loadfactory*capacity);

  2)判斷當前table的位置是否存在entry。

  經上兩個條件聯合判定,才會進行數組的擴容工作,最后擴容完成才會去創建新的entry。

  而擴容的方法即為:resize()看代碼

  

 1 void resize(int newCapacity) {
 2         //拿到原table對象
 3         Entry[] oldTable = table;
 4         //計算原table的桶長度
 5         int oldCapacity = oldTable.length;
 6         //先判定,當前容量是否已經是最大容量了(2的30次方)
 7         if (oldCapacity == MAXIMUM_CAPACITY) {
 8             //假如達到了,將閾值設為int的最大值2的31次方減1,返回
 9             threshold = Integer.MAX_VALUE;
10             return;
11         }
12         //創建新的table對象
13         Entry[] newTable = new Entry[newCapacity];
14         //將舊的table放入新的table中
15         transfer(newTable, initHashSeedAsNeeded(newCapacity));
16         //賦值新table
17         table = newTable;
18         //計算新的閾值
19         threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
20     }
21 //具體的擴容過程
22 void transfer(Entry[] newTable, boolean rehash) {
23         int newCapacity = newTable.length;
24         //遍歷原table,重新散列
25         for (Entry<K,V> e : table) {
26             while(null != e) {
27                 Entry<K,V> next = e.next;
28                 if (rehash) {
29                     e.hash = null == e.key ? 0 : hash(e.key);
30                 }
31                 int i = indexFor(e.hash, newCapacity);
32                 e.next = newTable[i];
33                 newTable[i] = e;
34                 e = next;
35             }
36         }
37     }

至此,擴容就說完了。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM