HashMap實現原理

2021-07-30 14:54:51 字數 2836 閱讀 2161

資料結構中有陣列和鍊錶來實現對資料的儲存,但這兩者基本上是兩個極端。

陣列儲存區間是連續的,占用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為o(1);陣列的特點是:定址容易,插入和刪除困難;

鍊錶儲存區間離散,占用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達o(n)。鍊錶的特點是:定址困難,插入和刪除容易。

那麼我們能不能綜合兩者的特性,做出一種定址容易,插入刪除也容易的資料結構?答案是肯定的,這就是我們要提起的雜湊表。雜湊表((hash table)既滿足了資料的查詢方便,同時不占用太多的內容空間,使用也十分方便。

雜湊表有多種不同的實現方法,我接下來解釋的是最常用的一種方法—— 拉鍊法,我們可以理解為「鍊錶的陣列」 ,如圖:

從上圖我們可以發現雜湊表是由陣列+鍊錶組成的,乙個長度為16的陣列中,每個元素儲存的是乙個鍊錶的頭結點。那麼這些元素是按照什麼樣的規則儲存到陣列中呢。一般情況是通過hash(key)&(leng-1)獲得,也就是元素的key的雜湊值和(陣列長度-1)進行與操作 。比如上述雜湊表中,12&15=12,28&15=12,108&15=12,140&15=12。所以12、28、108以及140都儲存在陣列下標為12的位置。

下面這些的**還是以hash%length來確定陣列位置,但是在jdk8裡已經是用hash(key)&(length-1)來確定位置,因為這種計算效率更高

hashmap基本結構

static final int default_initial_capacity = 1 << 4;      // hashmap初始容量大小(16) 

static final int maximum_capacity = 1 << 30;               // hashmap最大容量

static final float default_load_factor = 0.75f;          // 負載因子

hashmap的容量size乘以負載因子[預設0.75] = threshold;  // threshold即為開始擴容的臨界值

transient entry table = (entry) empty_table;    // hashmap的基本構成entry陣列

entry基本構成

static class entryimplements map.entry 

hashmap其實也是乙個線性的陣列實現的,所以可以理解為其儲存資料的容器就是乙個線性陣列。這可能讓我們很不解,乙個線性的陣列怎麼實現按鍵值對來訪問資料呢?這裡hashmap有做一些處理。

首先hashmap裡面實現乙個靜態內部類entry,其重要的屬性有key , value, next

,從屬性key,value我們就能很明顯的看出來entry就是hashmap鍵值對實現的乙個基礎bean,我們上面說到hashmap的基礎就是乙個線性陣列,這個陣列就是entry,map裡面的內容都儲存在entry裡面。

/**

* the table, resized as necessary. length must always be a power of two. */

transiententry table;

既然是線性陣列,為什麼能隨機訪問?這裡hashmap用了乙個小演算法,大致是這樣實現:

// 儲存時:

inthash = key.hashcode(); // 這個hashcode方法這裡不詳述,只要理解每個key的hash是乙個固定的int值

intindex = hash % entry.length;

entry[index] = value;

// 取值時:

inthash = key.hashcode();

intindex = hash % entry.length;

returnentry[index];

疑問:如果兩個key通過hash%entry.length得到的index相同,會不會有覆蓋的危險?

這裡hashmap裡面用到鏈式資料結構的乙個概念。上面我們提到過entry類裡面有乙個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的大致實現,我們應該已經清楚了。

publicv put(k key, v value)while(e !=null);

拉鍊法主要就是在發生hash衝突時,將相同hash值的放進同乙個煉表裡,但這樣有乙個缺點,就是當遇到最壞的情況,所有的元素的hash都衝突,這時所有的元素都會放在乙個煉表裡,這時查詢元素的複雜度就變成o(n) jdk8對此用了平衡樹的方式進行了優化,複雜度變成o(logn)

HashMap實現原理

hashmap 的get 方法 呼叫get方法返回entry public v get object key getentry方法 final entrygetentry object key 對key int hash key null 0 hash key for entrye table in...

HashMap實現原理

public v put k key,v value 如果i索引處的entry為null,表明此處還沒有entry。modcount 將key value新增到i索引處。addentry hash,key,value,i return null 從上面的源 中可以看出 當我們往hashmap中put...

HashMap實現原理

hashmap底層是通過陣列加鍊表的方式實現的,首先假如定義乙個陣列長度為5,當新增元素時,根據key的hashcode值 5進行取餘,如果餘值為3,則放在陣列的第四個位置,如果第四個位置已經有值,則會發生hash碰撞問題,所以hashmap又採用了鍊錶的方式進行儲存,如果key值存在則覆蓋,如果不...