Java資料結構 HashMap 原始碼閱讀

2021-07-31 02:36:17 字數 4583 閱讀 1336

hashmap就是資料結構中的雜湊表,是以key,value的形式進行儲存資料的,陣列具有查詢定位快,但是插入操作效能差,鍊錶具有查詢慢插入快速的特點,而hashmap可以說是這兩種方式的一種折中。

hashmap採用陣列與煉表相結合的方式實現,如下圖所示

*hashmap會根據儲存實體key的值確定存放在那個陣列的鍊錶上

1. 可以儲存null值(hashmap可以接受為null的鍵值(key)和值(value))

2. 非執行緒安全

3. 儲存快查詢快

hashmap要想充分利用陣列與鍊錶的效能達到比較高的效能,那麼影響效能的因素有哪些?

1. 雜湊函式均勻

因為hashmap是通過hash函式來定位陣列下標,進而確定物件儲存位置的,最壞的情況是通過hash函式計算出的下標都為相同,那麼hashmap就退化成煉表了,最好的情況是都不相同那麼就能達到o(1)的效率,所以hash計算出來衝突產生越多,那麼查詢效率就越低。衝突越少查詢效率越高。

2. 處理衝突的方法

既要有較高的查詢效能,又要有較高的插入效能,那麼衝突就無法避免,解決衝突的方式也決定了其效能的優劣。

hashmap是採用拉鍊法解決衝突的,如果key值的hash值計算出來衝突,那麼就在陣列相應下標的最後插入鍊錶節點。

1.首先根據key呼叫hashcode方法生成hashcode

2.呼叫hash方法根據生成的hashcode生成hash值

3.利用hash值與陣列table的size-1求餘得到陣列下標

注:求餘就是為了使得得到的陣列在陣列區間內

public

inthashcode()

hash = h;

}return h;

}

hashcode的生成

string類的value是char,char可以轉換成utf-8編碼。譬如,』a』、』b』、』c』的utf-8編碼分別是97,98,99;「abc」根據上面的**轉換成公式就是:

h = 31 * 0 + 97 = 97;

h = 31 * 97 + 98 = 3105;

h = 31 * 3105 + 99 = 96354;

使用31作為乘數因子是因為它是乙個比較合適大小的質數:如值過小,當參與計算hashcode的項數較少時會導致積過小;如為偶數,則相當於是左位移,當乘法溢位時會造成有規律的位資訊丟失。而這兩者都會導致重複率增加。

這樣就得到了重複率較低的hashcode。

static

final

int hash(object key)

hash值的計算演算法如上所示:如果取到key不等於null,則取 (h = key.hashcode()) ^ (h >>> 16)。這裡為什麼要這樣做?

你會發現只要二進位制數後4位不變,前面的二進位制位無論如何變化都會出現相同的結果。為了防止出現這種高位變化而低位不變導致的運算結果相同的情況,因此需要將高位的變化也加入進來,而將整數的二進位制位上半部與下半部進行異或操作就可以得到這個效果。

從hashcode的設計到hash函式的處理都是為了降低重複率,使得資料均勻分布

為什麼要使用與運算?

因為下標的計算公式就是hash值% tablesize,當tablesize是2的n次方(n≥1)的時候,等價於hash值 & (tablesize - 1)。

先來看一看乙個有意思的方法

static

final

int tablesizefor(int cap)

上面的方法就是陣列大小生成的方法,永遠生成的是乙個2的冪的數。也就是說例如輸入15 返回結果就是 16 。

這個方法乍一看不明所以,但是為什麼它總能在給定乙個值之後返回乙個大於它或者等於它同時最接近它的2的冪?

我們看下下面這段二進位制資料:

2的0次方2進製  0000 0001    十進位制 1

2的1次方2進製 0000 0010 十進位制 2

2的2次方2進製 0000 0100 十進位制 4

2的3次方2進製 0000 1000 十進位制 8

2的4次方2進製 0001 0000 十進位制 16

我們發現臨近的兩個2的冪之間的高位是相鄰的。比如說2的0次方最後一位是1,2的一次方倒數第二位是1,2的3次倒數第三位是1。

例如7要取到8 所以將

0000

0111

無符號右移

0000

0111 >>>1 等於 0000

0011

進行與操作 (只要存在1那麼是1)

0000

0111

| 0000 0011 等於 0000 0111

n + 1(原始碼三目運算子)

0000

0111 + 0000

0001=0000

1000

給定數字為4最後通過右移變成8

0000

0100

無符號右移1

0000

0100 >>>1 等於 0000

0010

進行與操作 (只要存在1那麼是1)

0000

0100

| 0000 0010 等於 0000 0110

接著因為已經有2位達到變成1的目的,所以接著就是移動2位

0000

0110 >>> 0000

0011

進行與操作 (只要存在1那麼是1)

0000

0110

| 0000 0011 等於 0000 0111

n + 1(原始碼三目運算子)

0000

0111 + 0000

1000 =8

看到上面應該明白了是什麼意思了吧?

其實就是借助兩個2的冪之間的高位是相鄰的的方式。然後通過無符號右移使得給定的數高位以後全變成1這樣最後進行n+1就獲取到了最小大於它的2的冪。

至於原始碼為什麼到了16就不操作了,是因為int型別是佔4個位元組,每個位元組8位,共32位。而向右移動16位後,可以從高位第乙個出現1的位置開始向右連續32位為1,已經超越了int的最大值,所以不用在進行位移操作了,這也是**中只是移動16位後就結束的原因。

等等上面的示例有問題,給定數字4為什麼返回8,不是應該返回4嗎?別急這就是為什麼**開始要下面這一行

int n = cap -1;
如果不加這個那麼就會使得結果是原來的兩倍。

(1)容量選擇

hashmap的初始容量是 1 << 4,也就是16。以後每次擴容都是size << 1,也就是擴容成原來容量的2倍。如此設計是因為 2^n-1(n≥1)的二進位制表示總是為重複的1,方便進行求餘運算。前文介紹過。

(2)裝載因子

裝填因子預設是0.75,也就是說如果陣列容量為16,那麼當key的數量大於12時,hashmap會進行擴容。

裝填因子設計成0.75的目的是為了平衡時間和空間效能。過小會導致陣列太過於稀疏,空間利用率不高;過大會導致衝突增大,時間複雜度會上公升

final v putval(int hash, k key, v value, boolean onlyifabsent,

boolean evict)

//如果迴圈過程中發現新舊key的值相同,跳**是否覆蓋舊值

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;}}

//是否覆蓋舊值

v oldvalue = e.value;

//如果新值不為空且(允許修改舊值 或 舊值為空),覆蓋舊節點的值

if (!onlyifabsent || oldvalue == null)

e.value = value;

afternodeaccess(e);

return oldvalue;}}

//用於比較判斷是否在遍歷集合過程中有其它執行緒修改集合,詳情請網上搜尋fail-fast快速失敗機制

++modcount;

if (++size > threshold)

resize();

afternodeinsertion(evict);

return

null;

}

public v get(object key) 

final nodegetnode(int hash, object key) while ((e = e.next) != null);}}

return

null;

}

Java 資料結構 HashMap

hashmap 特點 1.map 無序不可重複 2.hash 通過map的key的hashcode的一次hash來決定儲存位置,通過key的 與equals方法來確定是否重複 原始碼分析 public v put k key,v value 如果 i 索引處的 entry 為 null,表明此處還沒...

資料結構 手寫hashmap

define size 100 位址鏈個數,足夠大 class simhash public simhash simhash delete map 清除陣列 void insert int key,int value node p map hash key 確定位址鏈索引 node q new no...

hashMap的資料結構

在jdk8中,hashmap是用了陣列和鍊錶以及紅黑樹這三種資料結構 首先,在hashmap類中,都有乙個table陣列,我們在儲存資料時,對這個資料的hash值進行一系列的計算 計算出它在table中的位置 下標 並將它存放進去 然而,我們在hashmap是什麼 中提到,不同的物件的hash值可能...