HashMap原理與底層理解

HashMap原理與底層理解

背景

又一次去面試,還是在java上栽了些跟頭。還好我心態好,面試官也挺好,大家就是交流學習嘛,每一次的面試都是個交流學習的機會,就當作他給我上課了,我能進步一點點也是好的,把每一次的面試都當作進步和查漏補缺的機會,逐步試錯,逐步學習,做好了準備機會自然就到了。

HashMap的基本原理

概念

提到HashMap的概念和認識,HashMap基於Map接口實現,元素以鍵值對的方式存儲,並且允許使用null鍵和null值,因爲key不允許重複,因此只能有一個鍵爲null,另外HashMap不能保證放入元素的順序,它是無序的,和放入的順序並不能相同。HashMap是線程不安全的。(當然這些都是最基本的,只要用過都能答到這個水平,博主就是答到的這個水平,手動苦笑~)

數據存儲結構

java7中HashMap由數組和鏈表來實現對數據的存儲,HashMap採用Entry數組來存儲key-value對,每一個鍵值對組成了一個Entry實體,Entry類實際上是一個單向的鏈表結構,它具有Next指針,可以連接下一個Entry實體,以此來解決Hash衝突的問題。
數組存儲區間是連續的,佔用內存嚴重,故空間複雜的很大。但數組的二分查找時間複雜度小,爲O(1);數組的特點是:尋址容易,插入和刪除困難;
鏈表存儲區間離散,佔用內存比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N)。鏈表的特點是:尋址困難,插入和刪除容易。
capacity:當前數組容量,初始默認容量爲16,始終保持 2^n,可以擴容,擴容後數組大小爲當前的 2 倍。(爲什麼必須爲2的次冪?當計算索引值index = h % length 由於計算機的取餘操作速度很慢,而計算機的按位取餘 & 的操作非常快,又因爲 h%length = h & (length-1) (需要滿足length = 2^n) 因此規定了length = 2^n 加快index的計算速度 )
loadFactor:負載因子,默認爲 0.75。 (loadFactor負載因子是一個非常重要的參數,因爲他能夠反映HashMap桶數組的使用情況, 這樣的話,HashMap的時間複雜度就會出現不同的改變。當這個負載因子屬於低負載因子的時候,HashMap所能夠容納的鍵值對數量就是偏少的,擴容後,重新將鍵值對 存儲在桶(bucket)數組中,鍵與鍵之間產生的碰撞會下降,鏈表的長度也會隨之變短。)
threshold:擴容的閾值,等於 capacity * loadFactor。
java8中,利用了紅黑樹,所以在JAVA 8 是由數組+鏈表+紅黑樹組成。當鏈表中的元素超過8個之後,會將鏈表轉換爲紅黑樹,在這些位置進行查找的時候可降低時間複雜度 爲O(logn)。

擴容(Resize)

當hashmap中的size > loadFactory * capacity即會發生擴容,擴容(resize)就是重新計算容量,向HashMap對象裏不停的添加元素,而HashMap對象內部的數組無法裝載更多的元素時,對象就需要擴大數組的長度,以便能裝入更多的元素。經過resize之後,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。

添加方法 —— put()

添加鍵值對時,首先進行table是否初始化的判斷,如果沒有進行初始化(分配空間,Entry[]數組的長度)。然後進行key是否爲null的判斷,如果key==null ,放置在Entry[]的0號位置。計算在Entry[]數組的存儲位置,判斷該位置上是否已有元素,如果已經有元素存在,則遍歷該Entry[]數組位置上的單鏈表。判斷key是否存在,如果key已經存在,則用新的value值,替換點舊的value值,並將舊的value值返回。如果key不存在於HashMap中,程序繼續向下執行。將key-vlaue, 生成Entry實體,添加到HashMap中的Entry[]數組中。

獲取方法 —— get()

首先計算hash值,然後調用indexFor()方法得到該key在table中的存儲位置,得到該位置的單鏈表,遍歷列表找到key和指定key內容相等的Entry,返回entry.value值。

面試中的問題

1.當兩個對象的hashcode相同會發生什麼?
答:因爲hashcode相同,所以它們的bucket位置相同,‘碰撞’會發生。無論何時,HashMap 的每個“桶”只存儲一個元素(也就是一個 Entry),由於 Entry 對象可以包含一個引用變量(就是 Entry 構造器的的最後一個參數)用於指向下一個 Entry,因爲HashMap使用鏈表存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。

2.如果兩個鍵的hashcode相同,你如何獲取值對象?
答:調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,然後獲取值對象。如果有兩個值對象儲存在同一個bucket,將會遍歷LinkedList直到找到值對象。找到bucket位置之後,會調用keys.equals()方法去找到LinkedList中正確的節點,最終找到要找的值對象。

3.爲什麼String, Interger這樣的wrapper類適合作爲鍵?
答:String, Interger這樣的wrapper類作爲HashMap的鍵是再適合不過了,而且String最爲常用。因爲String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。

4.hashCode和equals區別?
答:hashCode實際上返回一個int整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。equals它的作用是判斷兩個對象是否相等,如果對象重寫了equals()方法,比較兩個對象的內容是否相等;如果沒有重寫,比較兩個對象的地址是否相同,價於“==”。如果兩個對象相等,那麼它們的hashCode()值一定相同。這裏的相等是指,通過equals()比較兩個對象時返回true。如果兩個對象hashCode()相等,它們並不一定相等。因爲在散列表中,hashCode()相等,即兩個鍵值對的哈希值相等。然而哈希值相等,並不一定能得出鍵值對相等,此時就出現所謂的哈希衝突場景。

總結

這次面試我就被HashMap的hashCode一致的話會怎麼辦問到了,在此記錄,鞭策自己,保持進步。

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