目錄
一、資料模型
二、重要屬性
三、構造方法
四、普通方法
put()
resize()
get()
五、總結
在網上看過一些所謂的hashmap原始碼分析,大部分依舊是比較抽象的。究其原因,主要還是對hashmap的資料結構不理解。以下以圖示展示。
hashmap中存在乙個內部類:
static class nodeimplements map.entry
該內部類中,有四個屬性,我們儲存的是key和value。其中hash通過key計算的hashcode值,next是指向下乙個節點。由此可以理解儲存原理了。
/**
* the default initial capacity - must be a power of two.
*/// 思考:為什麼必須是2的倍數?
static final int default_initial_capacity = 1 << 4; // aka 16
// 最大長度
static final int maximum_capacity = 1 << 30;
int threshold;
threshold = capacity * loadfactor,當size>=threshold的時候,那麼就要考慮對陣列的擴增了,也就是說,這個的意思就是衡量陣列是否需要擴增的乙個標準。同時需要對應陣列上有元素。
static final float default_load_factor = 0.75f;
負載因子:該屬性是表示在擴容之前容量占有率的乙個標尺。它控制陣列存放node是的疏密程度。loadfactor越趨近於1,那麼陣列中存放的資料(entry)也就越多,也就越密,也就是會讓鍊錶的長度增加,loadfactor越小,也就是趨近於0,那麼陣列中存放的資料也就越稀。預設的0.75f是乙個權衡後的值。
static final int treeify_threshold = 8;
如果鍊錶超過了這個值,則將單鏈表轉變為紅黑樹。(1.8版本)
static final int untreeify_threshold = 6;
如果紅黑樹的節點被刪,且小於該閾值,則變為鍊錶。
transient int size;
記錄陣列已使用的容量,用來個閾值進行比較。
final float loadfactor;
填充因子
static final int min_treeify_capacity = 64;
桶中結構轉化為紅黑樹對應的table的最小大小
// 建構函式一
public hashmap()
// 建構函式二
public hashmap(int initialcapacity)
// 建構函式三
public hashmap(int initialcapacity, float loadfactor)
// 建構函式四
public hashmap(map extends k, ? extends v> m)
我們用的最多的就是第乙個構造方法,負載因子預設是0.75f。建構函式三是手動設定初始容量和負載因子,建構函式二呼叫的建構函式三,手動設定初始容量大小。建構函式是將別的map對映到自己的map中。
上面我們了解了hashmap的儲存結構。在給hashmap中新增元素的時候,我們應該考慮這樣乙個問題:如何確定元素的新增的位置?
我們來分析**:
public v put(k key, v value)
final v putval(int hash, k key, v value, boolean onlyifabsent,boolean evict)
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;}}
++modcount;
// 如果陣列中的元素個數超過閾值,則進行擴容
if (++size > threshold)
resize();
afternodeinsertion(evict);
return null;
}
通過以上的原始碼,我們可以看出。在新增新節點時,通過hash確定其位置。分以下種情況:
確定位置時,hash值起著重要的作用,我們看看該方法:
static final int hash(object key)
這裡通過位運算重新計算了hash值的值。為什麼要重新計算?
主要是因為n值比較小,hash只參與了低位運算,高位運算沒有用上。這就增大了hash值的碰撞概率。而通過這種位運算的計算方式,使得高位運算參與其中,減小了hash的碰撞概率,使hash值盡可能散開。
注意:我們在分析重要屬性default_initial_capacity的時候,陣列的初始化或者擴容為什麼給必須是2的n次冪?
在以上原始碼的(n - 1) & hash
中給出了答案:(n - 1) & hash
等價於對 length 取餘。但取餘的計算效率沒有位運算高,所以(n - 1) & hash
也是乙個小的優化。例如,假設 hash = 185,n = 16。
這個方法我們在put()方法中已經呼叫過,作用是用來進行陣列的初始化和擴容。下面看看原始碼:
final node resize()
// 容量翻倍,使用左移,效率更高
else if ((newcap = oldcap << 1) < maximum_capacity &&
oldcap >= default_initial_capacity)
// 閾值翻倍
newthr = oldthr << 1; // double threshold
}// 之前閾值大於0
else if (oldthr > 0)
newcap = oldthr;
// oldcap = 0並且oldthr = 0,使用預設值(如使用hashmap()建構函式,之後再插入乙個元素會呼叫resize函式,會進入這一步)
else
// 新閾值為0
if (newthr == 0)
threshold = newthr;
@suppresswarnings()
// 初始化table
node newtab = (node)new node[newcap];
table = newtab;
// 之前的table已經初始化過
if (oldtab != null)
else
} while ((e = next) != null);
if (lotail != null)
if (hitail != null) }}
}}return newtab;
}
其實仔細分析源**,基本上只要理解了hashmap的結構,其餘的邏輯也就和 清楚了。
public v get(object key)
final nodegetnode(int hash, object key) while ((e = e.next) != null);}}
return null;
}
一直都覺得hashmap的原始碼很簡單,仔細看看,其實不然。其中涉及了很多很多的知識點,比如資料結構就涉及了陣列、鍊錶和紅黑樹。其餘的細節都是數不勝數。細細研讀該原始碼一整天,都沒有將其中的細節詳盡解析。
1.資料結構是基礎,也是靈魂(通常伴隨演算法)。只有對資料結構了然於心,才能詳細了解底層原理;
2.伴隨jdk公升級的過程中,我們能很明顯感受到一些優化。比如將十進位制的值優化為二進位制的值;十進位制的計算優化為位運算;建構函式中,不給出長度,而是在新增第乙個元素的時候給出長度(預設長度或者設定的長度);
3.put()方法是hashmap的核心,弄清楚了這個方法,其餘的源**都不難;
4.讀原始碼一定要靜下心來,心不靜,就只能浮在表面;
5.個人水平和精力有限,難免有漏洞,希望大家多多指教。
HashMap原始碼分析
public hashmap int initialcapacity,float loadfactor 2 接下來是重要的put方法,put方法用於將鍵值對儲存到map中,讓我們來具體分析一下。public v put k key,v value if key null 若key為null,則將va...
HashMap 原始碼分析
1 getentry object key 方法 final entrygetentry object key return null 根據key的hash值計算出索引,得到table中的位置,然後遍歷table處的鍊錶 for entrye table indexfor hash,table.le...
HashMap原始碼分析
public v put k key,v value if key null return putfornullkey value int hash hash key int i indexfor hash,table.length for entrye table i e null e e.nex...