前言:
幾年前剛剛培訓java出來,參加的第一家面試,面試java基礎,第一個問題就是hashmap數據結構。
當時我的反應是這樣的:
啥?啥結構,什麼數?什麼構?不就是get、put?
後來經過百度,看各種帖子,也算是瞭解了一些,面試時也算是能跟面試官說說hashmap數據結構了,畢竟天天問。問道最後也能“談笑風聲”了。。。。
後來在工作之餘慢慢看了數據結構這本數,看了下數據結構的視頻(這裏強推趙海英的視頻),發現當時的我還真是圖樣圖森破、sometime naive。
正文:
網上許多帖子也都提到:hashMap數據結構是數組+鏈表,但是並沒有說爲什麼要做成數組加鏈表,這樣做有什麼好處,hash查找到底是什麼這些都沒又提到,下面說說hash又名散列,在hashMap中的使用。
一: Map,在hashMap中存的最小單元便是Entry,裏面存放key和value。
二:重點在於java中hashMap中的hash是怎麼存儲這個數據的
1.hash函數
這些數據會存在一個數組中,hashMap的數組結構:
這個數組中,hash是如何實現這個快速查找的,比如下面的這一個數組中存入了許多
我們要通過key去取value的時候,需要便利這一整個數組,將每一個數據拿出來,去比較key,相同的話便取出來。這樣一來取出數據的時間複雜度便是O(n),也就是說一個長度爲n的數組,取出數據的最多比較次數是n次。那麼有沒有一種方法只要比較一次就能取出value的呢?hash(散列)就是實現這個事情的。
java中代碼是這樣的:
用過key,進行hash函數計算,直接取到節點。
根據key的hashCode計算出一個hash值(>>>移位符,右移一位最大值添0,正數時相當於除2,^異或運算)
注:爲了方便理解,下面方法沒截全
根據計算出來的hash值和數組長度,直接計算出存放數據的索引。這樣hash便實現了只需一次就可以取出value的方法。
以上就是java通過hash函數直接確定數據索引的過程。
注:&與運算,在這個地方(length-1)&hash,其中length在hashmap中是2的N次方,所以這個計算所算出來的值相當於hash%length。(2的N次方的二進制一定是1111...11這種數字,所以在使用&運算的時候相當於取餘)所以java中hashMap的hash函數使用的是除留取餘法。
2.hash衝突
通過hash函數可以之間計算出索引了,但是在put的時候,插入對象的key計算出的hash值的索引在數組中已經存在了數據怎麼辦,在hash查找中不可避免出現hash衝突。java中便是使用鏈表來處理hash衝突的(注:在java8中鏈表過長時會變成樹)
源碼:
以上便是爲何hashMap是數組加鏈表結構。並且如何實現快速hash查找。
3.hash擴容
當hashMap中數據不斷增多,hash衝突也逐漸增多。數組中的鏈表也會越來越長,這時hash函數所帶來的快速查找優勢便逐漸消失,因爲在鏈表中查找便需要重頭到尾遍歷。
所以在數據到達一定數量時便會執行resize方法,重新組成hashMap
這個方法中便會重新排布hashMap。
這裏有一個重要的變量裝載因子:
如果它的值大的話,hashMap中的元素的hash衝突會增多鏈表會越拉越長,get效率低;值小的話hash衝突降低,但是執行resize的次數也會更加頻繁,put的效率會降低。java中默認的是0.75。
所以p3c插件也會提示
在hashMap初始化時指定大小,減少resize的情況。
以上便是java中對hashMap中的一些操作。
所以在談起hash結構時,主要可以基於三點:hash函數、hash衝突解決、裝載因子。