HashMap筆試面試題彙總解析
在筆試和麵試的過程中,Java集合框架毫無疑問是考察的重點,貌似面試官對這都情有獨鍾,而有關HashMap的考察更是重中之重和難點,一個小小的HashMap不僅能反應出你對Java集合的掌握程度,更能反映出面試者對資料結構的熟悉情況和設計資料結構的思維能力,很容易在這個問題上尷尬地看著面試官發呆,搞不好就拜拜了。根據個人對這個問題的理解和一些網上的資料在這裡做個彙總解析,與各位共享。廢話不多說,要想搞透徹HashMap,那咱們就先從HashMap的原理說起。
1.HashMap與雜湊表
HashMap從本質上說就是雜湊表,其底層實現就是圍繞雜湊表展看的,搞明白了這個很多問題就很容易理解了。那雜湊表又是什麼東東呢?
雜湊表的核心思想就是讓記錄的關鍵字和儲存位置建立一一對映關係,這樣我們就可以通過Key直接獲得相對應的Value,好比我們通過索引可以直接獲得陣列對應的某個值一樣,而這種一一對映關係要通過某個數學函式來構造出來,這個函式就是所謂的雜湊函式。
而雜湊函式有五種實現方式:
A. 直接定址法:取關鍵字的線性函式值作為雜湊地址。
B. 數字分析法:取關鍵字的中的若干位作為雜湊地址。
C. 平方取中法:取關鍵字平方後的中間幾位作為雜湊地址。
D. 摺疊法:將關鍵字分割成位數相同的幾部分(最後一部分可以不同),然後取這幾部分的疊加和作為雜湊地址。
E. 除留餘數法:H(key) = key MOD p ,p<=m
F. 隨機函式法
上述五中實現方式中最常用的是除留餘數法,而通過雜湊函式定址的過程可能出現“衝突”------即若干個不同的key卻對應相同的雜湊地址。解決雜湊衝突有如下的方法:
A. 開放地址法:H=(H(kyt)+d) MOD m ,m為雜湊表表長。
(1)d=1,2,3------> m-1 時,稱謂線性探測再雜湊
(2)d=1^2,-1^2---->+(-)k^2時,稱為二次線性再雜湊。
(3)d為偽隨即序列時,稱為偽隨即序列再雜湊。
B .再雜湊法
H=RH(key),RH()為不同的雜湊函式,即在地址衝突時計算另一個雜湊函式地址,直到不再發生衝突。
C .鏈地址法
將所有雜湊地址衝突的記錄儲存在同一個線性連結串列中
D 公共溢位區法
將所有雜湊地址衝突的記錄都填入到溢位表中
而HashMap的實現與雜湊函式的選擇和雜湊地址衝突的解決方案密切相關,詳情繼續看下文。
2.HashMap的具體實現
HashMap的實現採用了除留餘數法形式的雜湊函式和鏈地址法解決雜湊地址衝突的方案。這樣就涉及到兩種基本的資料結構:陣列和連結串列。陣列的索引就是對應的雜湊地址,存放的是連結串列的頭結點即插入連結串列中的最後一個元素,連結串列存放的是雜湊地址衝突的不同記錄。
連結串列的結點設計如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
}
next作為引用指向下一個記錄。在HashMap中設計了一個Entry型別的陣列用來存放Entry的例項即連結串列結點。
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
除留餘數法形式的雜湊函式:
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1); //和除留餘數等價
}
當我們往HashMap中put元素的時候,先根據key的hashCode重新計算hash值,根據hash值得到這個元素在陣列中的位置(即下標),如果陣列該位置上已經存放有其他元素了,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾,陣列中儲存的是最後插入的元素 。如果陣列該位置上沒有元素,就直接將該元素放到此陣列中的該位置上。
以上是有關HashMap底層實現的說明,幾乎所有有關HashMap的題目都是基於上述理論展看的。下面是收集到的一些HashMap相關的筆試面試題的解析:
1.HashMap與Hashtable的區別:
HashMap可以接受null鍵值和值,而Hashtable則不能。
Hashtable是執行緒安全的,通過synchronized實現執行緒同步。而HashMap是非執行緒安全的,但是速度比Hashtable快。
2.HashMap的原理
參見上文中的HashMap的具體實現
3.當兩個物件的hashcode相同怎麼辦
當雜湊地址衝突時,HashMap採用了鏈地址法的解決方式,將所有雜湊地址衝突的記錄儲存在同一個線性連結串列中。具體來說就是根據hash值得到這個元素在陣列中的位置(即下標),如果陣列該位置上已經存放有其他元素了,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。
4.如果兩個鍵的hashcode相同,你如何獲取值物件
HashMap在連結串列中儲存的是鍵值對,找到雜湊地址位置之後,會呼叫keys.equals()方法去找到連結串列中正確的節點,最終找到要找的值物件
5.如果HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦
HashMap預設的負載因子大小為0.75,也就是說,當一個map填滿了75%的空間的時候,和其它集合類(如ArrayList等)一樣,將會建立原來HashMap大小的兩倍的陣列,來重新調整map的大小,並將原來的物件放入新的陣列中。
6.為什麼String, Interger這樣的wrapper類適合作為鍵?
String, Interger這樣的wrapper類是final型別的,具有不可變性,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的物件。
7.ConcurrentHashMap和Hashtable的區別
Hashtable和ConcurrentHashMap有什麼分別呢?它們都可以用於多執行緒的環境,但是當Hashtable的大小增加到一定的時候,效能會急劇下降,因為迭代時需要被鎖定很長的時間。因為ConcurrentHashMap引入了分割(segmentation),不論它變得多麼大,僅僅需要鎖定map的某個部分,而其它的執行緒不需要等到迭代完成才能訪問map。簡而言之,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map。
8.HashMap的遍歷
第一種:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
效率高,以後一定要使用此種方式!
第二種:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以後儘量少使用!
可是為什麼第一種比第二種方法效率更高呢?
HashMap這兩種遍歷方法是分別對keyset及entryset來進行遍歷,但是對於keySet其實是遍歷了2次,一次是轉為iterator,一次就從hashmap中取出key所對於的value。而entryset只是遍歷了第一次,它把key和value都放到了entry中,即鍵值對,所以就快了。
先總結到這吧,以後遇到有價值的hashmap相關的題目日後再做更新,文章不足之處,反應各位拍磚指正。。。。
更多內容歡迎關注個人微信公眾號,一起成長!