一、 面試知識點
- 隨着18年以來現在互聯網對java面試題也是越問越深,其中hashmap更是java必問問題,那麼我們今天就來總結一下hashmap 的底層原理和麪試常考知識點。
- HashMap 是一種存儲高校但是不保證有序的容器,它的數據結構爲"數組+鏈表/紅黑樹"的結構(當鏈表長度到8以後數據結構改爲紅黑樹)
- 底層實現了Map<k,v> 的接口並實現了淺拷貝和序列化,HashMap 默認初始值大小爲16 ,初始值大小必須爲2的冪次,如果用戶輸入的不是2的冪,那麼系統自動更新爲輸入值附近的2的冪次,最大大小爲2的30次冪。HashMap的閾值默認爲 0.75,當存儲節點超過該值,對map進行擴容。
每次擴容爲原來的1倍。
- HashMap 提供了四種構造方法,分別是默認構造方法;可以指定初始容量構造方法;可以指定初始值和閾值的構造方法;以及基於一個Map的構造方法。一般常用的都是給定初始容量大小的構造方法
- 在一次put(添加操作)的時候,HashMap 會先進行初始化,如果沒有先進行初始化操作,初始化過程會取比用戶指定容量大的最近2的冪次數作爲數組的初始容量,如果設置了擴容的閾值也一併更新。初始化完成以後繼續put 方法
- 先判斷有沒有初始化
- 在判斷傳入的key是否爲空 就存儲在table(0)位置
- key不爲空就對key進行hash,hash的結果在 & 上數組的長度就得到了位置。
- 如果存儲位置爲空就創建新節點,不爲空就說存在hash衝突了。
- 解決衝突HashMap會遍歷整個鏈表,如果有相同的value值就更新,否則創建節點添加到鏈表頭。
- 添加還要判斷存儲節點是否達到閾值,達到閾值要進行擴容。
- 擴容兩倍,擴容的時候使用Arrays.copy() 進行擴容。
- 擴容過後新插入的節點也要重新進行hash 一遍才能插入。
// jdk8 源碼
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 取值的操作和添加差不多。
- 先判斷是否爲空,爲空就取table(0)去找
- 不爲空也低先hash & 數組長度得到下標位置
- 在遍歷找到相同的找到相應的值。
- 以上這些是一些常用的知識點,但是如果你只是知道以上這些還是不夠的,一般面試官還會問你HashMap 線程安全碼? 當然大家都知道是不安全的,但是要是問你怎麼解決呢? 如果你要是回答加鎖或者回答使用HashTable 基本上就掛了。HashMao是一個線程不安全的容器,在併發操作會出現丟失更新問題,嚴重會導致cpu宕機的,一般報錯爲java.util.ConcurrentModificationException.那我們該怎麼解決呢?
- 使用java類庫提供的collections工具包下的Collections.synchronizedMap(new HashMap()),返回一個線程安全的Map
- 使用併發包(java.util.concurrent)下的ConcurrentHashMap,ConcurrentHashMap採用分段式鎖機制實現線程安全。
- 能不能手寫一個HashMap 線程不安全的案例
private static void hashMapNotSafe() {
// 線程不安全版本
Map<String, String> hashMap = new HashMap<>();
//併發版本的hashMap。
// Map<String, String> hashMap = new ConcurrentHashMap<>() ;
for (int i = 0; i < 30; i++) {
new Thread(()-> {
hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(hashMap);
}, String.valueOf(i)).start();
}
}
- java8和java7的區別
- Hash1.7 和1.8 最大的不同在於1.8 採用了“數組+鏈表+紅黑樹”的數據結構,在鏈表長度超過8 時,把鏈表轉化成紅黑樹來解決HashMap 因鏈表變長而查詢變慢的問題;
- 1.7 的底層節點爲Entry,1.8 爲node ,但是本質一樣,都是Map.Entry 的實現
- 還有就是在存取數據時添加了關於樹結構的遍歷更新與添加操作,並採用了尾插法來避免環形鏈表的產生
二、 考點分析
HashMap 作爲最基礎的容器,常用來考1.7和1.8的區別,除了這個要想在面試中脫穎而出還要對HashMap的前因後果要多瞭解。
- 考點一:爲什麼初始容量爲2的冪等次?爲什麼負載因子爲0.75f?爲什麼做那麼多擾動處理。
- 這些問題都要圍繞一個點回答:減少hash 衝突。
- 容量必須爲2的冪次是爲了增加取值的可能性。
- 2的n次冪轉換爲二進制爲1後面n個0,在計算下標的是否爲hash&(length-1),也就是&(n-1)個1.所有二進制爲1的好處?
- 0/1 & 1 都爲它本身
- 0/1 & 0 都爲 0
- 可以看出&1保證了取值的平均。如果某一位爲0 ,比如最後一位,那麼它&出來下標就一定是個偶數,減少了HashMap 數組一半的取值,大大增加了衝突的可能。
- 2的n次冪轉換爲二進制爲1後面n個0,在計算下標的是否爲hash&(length-1),也就是&(n-1)個1.所有二進制爲1的好處?
- 負載因子爲0.75f是空間與時間的均衡。
- 如果負載因子小,意味着閾值變小。比如容量爲10 的HashMap,負載因子爲0.5f,那麼存儲5個就會擴容到20,出現哈希衝突的可能性變小,但是空間利用率不高。適用於有足夠內存並要求查詢效率的場景。
- 相反如果閾值爲1 ,那麼容量爲10,就必須存儲10個元素才進行擴容,出現衝突的概率變大,極端情況下可能會從O(1)退化到O(n)。適用於內存敏感但不要求要求查詢效率的場景
- hash()的意義在於使hash結果不同hash 算法的好壞直接印象hash結構的效率。1.8 之所以把9 次擾動降到2 次,是出於計算效率的考慮。
- 考點二:& 字符串和%字符串 雖然效果一樣,但是操作效果更高。
- 考點三:爲什麼int String 更適合做key?
- int 和 String 的好處在於hash 出來的值不會改變。如果是一個對象,那麼他們可能會因爲內部引用的改變而hashCode 值的改變,會導致存儲重複的數據或找不到數據的情況。
三、 面試時候由於HashMap 引導出來的其他問題?
- 不僅僅是HashMap 的問題,在面試的時候,面試官會引導出很多其他問題,所以這個地方你在回答問題的時候要設計引導到你熟悉的內容上
- 說說concurrentHashMap 和 HashMap 的區別以及底層的實現?
- 說說HashMap 如何實現有序(LinkHashMap 和TreeMap)以及他們的差別