HashMap的實現原理

2021-07-30 03:49:52 字數 3852 閱讀 8052

一。hashmap的資料結構

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

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

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

雜湊表:那麼我們能不能綜合兩者的特性,做出一種定址容易,插入刪除也容易的資料結構?答案是肯定的,這就是我們要提起的雜湊表。

雜湊表((hash table)既滿足了資料的查詢方便,同時不占用太多的內容空間,使用也十分方便。

0 --> 496 --> 896

1 --> 1 --> 337 --> 353

2 -->

3 -->

4 -->

5 -->

6 -->

7 -->

8 -->

9 --> 184

10 -->

11 -->

12 --> 12 --> 28 --> 108 --> 140 

13 -->

14 -->

15 -->  

從上圖我們可以發現雜湊表是由陣列+鍊錶組成的,乙個長度為16的陣列中,每個元素儲存的是乙個鍊錶的頭結點。那麼這些元素是按照什麼樣的規則儲存到陣列中呢。

一般情況是通過hash(key)%len獲得,也就是元素的key的雜湊值對陣列長度取模得到。

比如上述雜湊表中,12%16=12,28%16=12,108%16=12,140%16=12。所以key的雜湊值為12、28、108以及140都儲存在陣列下標為12的位置。

二。hashmap的訪問實現(hashmap其實也是乙個線性的陣列實現的, 乙個線性的陣列怎麼實現按鍵值對來訪問資料呢?)

1.首先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.

*/transient entry table;

2.儲存及取值的小演算法:

// 儲存時:

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

int index = hash % entry.length;

entry[index] = value;

// 取值時:

int hash = key.hashcode();

int index = hash % entry.length;

return entry[index];

3.put方法原始碼

疑問:如果兩個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的大致實現,我們應該已經清楚了。

public v put(k key, v value) 

}modcount++;

addentry(hash, key, value, i);

return null;

} //先取出對應位置上的原entry,把對應位置放入最新的entry,新entry的next指向原entry(為空就指向空)

void addentry(int hash, k key, v value, int bucketindex)

4.get方法原始碼

//會遍歷key的hash碼計算對應的table位置上的鍊錶,因為可能多個key的位置一樣,找到要尋找的key返回值。

public v get(object key)

return null;

}

4.null key的訪問

//null key總是存放在entry陣列的第乙個元素。 

private v putfornullkey(v value)

}modcount++;

addentry(0, null, value, 0);

return null;

} private v getfornullkey()

return null;

}

5.確定陣列index:hashcode % table.length取模

hashmap訪問時,都需要計算當前key應該對應entry陣列哪個元素,即計算陣列下標;演算法如下: 

/**

* returns index for hash code h.

*/static int indexfor(int h, int length)

按位取並,作用上相當於取模mod或者取餘%。

這意味著陣列下標相同,並不表示hashcode相同。

6.table初始大小 

public hashmap(int initialcapacity, float loadfactor)  

注意table初始大小並不是建構函式中的initialcapacity!! 而是 >= initialcapacity的2的n次冪!!!!

三。再雜湊rehash過程

當雜湊表的容量超過預設容量時,必須調整table的大小,這時,需要建立一張新錶,將原表的對映到新錶中。(當容量已經達到最大可能值時,那麼該方法就將容量調整到integer.max_value返回)

因為新的table變大了,因此原來table上的元素在新table中的位置需要重新計算,所以稱為再雜湊。

/**

* this method is called automatically when the

* number of keys in this map reaches its threshold.

*/void resize(int newcapacity)

entry newtable = new entry[newcapacity];

transfer(newtable);

table = newtable;

threshold = (int)(newcapacity * loadfactor);

}/**

* transfers all entries from current table to newtable.

*/void transfer(entry newtable) while (e != null);}}

}

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實現原理

資料結構中有陣列和鍊錶來實現對資料的儲存,但這兩者基本上是兩個極端。陣列儲存區間是連續的,占用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為o 1 陣列的特點是 定址容易,插入和刪除困難 鍊錶儲存區間離散,占用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達o n 鍊錶的特點是...

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...