深入剖析HashMap應用原理

Hashing(散列法或哈希法)的概念
散列法(Hashing)是一種將字符組成的字符串轉換爲固定長度(一般是更短長度)的數值或索引值的方法,稱爲散列法,也叫哈希法。由於通過更短的哈希值比用原始值進行數據庫搜索更快,這種方法一般用來在數據庫中建立索引並進行搜索,同時還用在各種解密算法中。
HashMap概念和底層結構
HashMap是基於哈希表的Map接口的非同步實現。此實現提供所有可選的映射操作,並允許使用null值和null鍵。HashMap儲存的是鍵值對,HashMap很快。此類不保證映射的順序,特別是它不保證該順序恆久不變。
HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。
數組:存儲區間連續,佔用內存嚴重,尋址容易,插入刪除困難;
鏈表:存儲區間離散,佔用內存比較寬鬆,尋址困難,插入刪除容易;
Hashmap綜合應用了這兩種數據結構,實現了尋址容易,插入刪除也容易。
hashMap的結構示意圖如下:
在這裏插入圖片描述
HashMap的基本存儲原理以及存儲內容的組成
基本原理:先聲明一個下標範圍比較大的數組來存儲元素。另外設計一個哈希函數(也叫做散列函數)來獲得每一個元素的Key(關鍵字)的函數值(即數組下標,hash值)相對應,數組存儲的元素是一個Entry類,這個類有三個數據域,key、value(鍵值對),next(指向下一個Entry)。
例如, 第一個鍵值對A進來。通過計算其key的hash得到的index=0。記做:Entry[0] = A。
第二個鍵值對B,通過計算其index也等於0, HashMap會將B.next =A,Entry[0] =B,
第三個鍵值對 C,index也等於0,那麼C.next = B,Entry[0] = C;這樣我們發現index=0的地方事實上存取了A,B,C三個鍵值對,它們通過next這個屬性鏈接在一起。我們可以將這個地方稱爲桶。 對於不同的元素,可能計算出了相同的函數值,這樣就產生了“衝突”,這就需要解決衝突,“直接定址”與“解決衝突”是哈希表的兩大特點。
HashMap的工作原理以及存取方法過程
HashMap的工作原理 :HashMap是基於散列法(又稱哈希法hashing)的原理,使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當我們給put()方法傳遞鍵和值時,我們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket(桶)位置來儲存Entry對象。”HashMap是在bucket中儲存鍵對象和值對象,作爲Map.Entry。並不是僅僅只在bucket中存儲值。
HashMap具體的存取過程如下:
put鍵值對的方法的過程是:在這裏插入圖片描述
①判斷鍵值對數組table[i]是否爲空或爲null,否則執行resize()進行擴容;
②根據鍵值key計算hash值得到插入的數組索引i,如果table[i]==null,直接新建節點添加,轉向⑥,如果table[i]不爲空,轉向③;
③判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裏的相同指的是hashCode以及equals;
④判斷table[i] 是否爲treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;
⑤遍歷table[i],判斷鏈表長度是否大於8,大於8的話把鏈表轉換爲紅黑樹,在紅黑樹中執行插入操作,否則進行鏈表的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;
⑥插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。
get值方法的過程是:
1、指定key 通過hash函數得到key的hash值
int hash=key.hashCode();
2、調用內部方法 getNode(),得到桶號(一般都爲hash值對桶數求模)
int index =hash%Entry[].length;
3、比較桶的內部元素是否與key相等,若都不相等,則沒有找到。相等,則取出相等記錄的value。
4、如果得到 key 所在的桶的頭結點恰好是紅黑樹節點,就調用紅黑樹節點的 getTreeNode() 方法,否則就遍歷鏈表節點。getTreeNode 方法使通過調用樹形節點的 find()方法進行查找。由於之前添加時已經保證這個樹是有序的,因此查找時基本就是折半查找,效率很高。
5、如果對比節點的哈希值和要查找的哈希值相等,就會判斷 key 是否相等,相等就直接返回;不相等就從子樹中遞歸查找。
HashMap中直接地址用hash函數生成;解決衝突,用比較函數解決。如果每個桶內部只有一個元素,那麼查找的時候只有一次比較。當許多桶內沒有值時,許多查詢就會更快了(指查不到的時候)。
HashMap中的碰撞探測(collision detection)以及碰撞的解決方法
當兩個對象的hashcode相同時,它們的bucket位置相同,‘碰撞’會發生。因爲HashMap使用LinkedList存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在LinkedList中。這兩個對象就算hashcode相同,但是它們可能並不相等。 那如何獲取這兩個對象的值呢?當我們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,遍歷LinkedList直到找到值對象。找到bucket位置之後,會調用keys.equals()方法去找到LinkedList中正確的節點,最終找到要找的值對象使用不可變的、聲明作final的對象,並且採用合適的equals()和hashCode()方法的話,將會減少碰撞的發生,提高效率。不可變性使得能夠緩存不同鍵的hashcode,這將提高整個獲取對象的速度,使用String,Interger這樣的wrapper類作爲鍵是非常好的選擇。
如何重新調整HashMap的大小
“如果HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?”
默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會創建原來HashMap大小的兩倍的bucket數組,來重新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫作rehashing,因爲它調用hash方法找到新的bucket位置。
文章較長,感謝您的閱讀。對文章如有疑問,歡迎提出。望分享的內容對大家有所幫助。蒐集整理了一些Java資料,包括Java進階學習路線以及對應學習資料,還有一些大廠面試題,需要的朋友可以自行領取:Java高級架構學習資料分享+架構師成長之路

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