JAVA中的數組,在添加或者刪除元素的時候,都會復制一個新數組,比較耗內存。但是數組的遍歷則是非常高效的。鏈表則是相反,遍歷慢(需要遍歷數組,一直找到值相等的元素才算找到),而添加和刪除元素代價低。
有沒有辦法結合兩者的特點,做到尋找元素快,插入元素或者刪除元素代價低呢?答案是利用哈利表。
HashMap put操作
這里寫圖片描述
當使用HashMap的put方法的時候,有兩個問題要解決:
1、長度為16的數組中,元素存儲在哪個位置
2、如果key出現hash沖突,如何解決
第一個問題,HashMap 是使用下面的算法來計算元素的存放位置的。
int hash = hash(key);
int i = indexFor(hash, table.length);
1
2
首先先hash,之后結合數組的長度進行一個&操作得到得到數組的下標。
第二個問題 則利用Entry類的next變量來實現鏈表,把最新的元素放到鏈表頭,舊的數據則被最新的元素的next變量引用着。
舉個例子,假設元素Entry<"1","1">通過hash算法算出存到下標為0的位置上,后面又添加一個Entry<"2","2">,
假設Entry<"2","2">通過hash算法算出也需要存到下標為0的數組中,那么此時鏈表是下面這個樣子的:
Entry<”2”,”2”> –> Entry<”1”,”1”>
也即是說,當key出現hash沖突的時候,鏈表中的第一個元素都是后面最新添加進來的那個,之前的則被next變量引用着。雖然這里是插入的動作,但是由於使用了鏈表,所以無需像數組的插入那樣,進行數組拷貝。
HashMap get操作
這個操作的原理就比較簡單,只需要根據key的hashcode算出元素在數組中的下標,之后遍歷Entry對象鏈表,直到找到元素為止。
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
這里有兩個注意點:
1、這里利用key的hashcode方法和equals方法,所以在使用HashMap的時候,如果使用對象作為key,最好覆寫key的hashcode和equals方法
不然可能出put到HashMap的時候,成功了,但是get的時候卻沒有找到數據
2、如果key hash沖突太多,會造成鏈表過長,在鏈表中查找元素的時候,會比較慢
hash沖突后優化方案
如果出現了大量hash沖突,那么遍歷鏈表的時候,會比較慢。JDK 1.8里面,當鏈表的長度大於閥值(默認為8)的時候,會使用紅黑樹來存儲數據,以便加快key的查詢速度。
總結
HashMap使用了數組+鏈表的方案,做到了讀取快,插入快的目的,但是HashMap還是一些使用上的問題的:
1、線程不安全
2、當容量不夠時,會進行rehash的流程,非常耗資源
————————————————
版權聲明:本文為CSDN博主「Sam_Deep_Thinking」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/linsongbin1/article/details/54667453