1.hashmap原始碼閱讀目標
了解具體的資料結構(hash及衝突鍊錶、紅黑樹)和重要方法的具體實現(hashcode、equals、put、resize...)
2.重要方法
hashcode 與 equals都是在abstractmap中定義的
hashcode是各元素hash的累加 h += iter.next().hashcode();
equals 1.是否是本身; 2.是否是map例項; 3.size是否相等; 4.比較每個value
重點在於put、resize具體實現步驟:
put:
1.tab為null或length為0 重新resize
2.位置hash(key) & (n-1)的元素為null,則直接賦值
3.既然對應位置的元素不為null,則要看它有什麼型別(單個元素(hash無衝突)或紅黑樹或鍊錶)
單個元素(新的元素如果與這個元素不相等)則要轉為鍊錶,鍊錶則可能轉為紅黑樹(轉化規則 >= 7)
++modcount
4.++size > threshold 則resize()
remove類似(<=6則轉化為鍊錶)
1treeifybin:final v putval(int hash, k key, v value, boolean
onlyifabsent,
2boolean
evict)
23if (e.hash == hash &&
24 ((k = e.key) == key || (key != null &&key.equals(k))))
25break
;26 p =e;27}
28}29if (e != null) 36}
37 ++modcount;
38if (++size >threshold)
39resize();
40afternodeinsertion(evict);
41return
null
;42 }
鍊錶轉為紅黑樹,紅黑樹較為複雜,所以將單獨另起一篇仔細研究學習
keyset/entryset:
1view codenewkeyset();
2foreach:
3int mc =modcount;
4for (int i = 0; i < tab.length; ++i)
8if (modcount !=mc)
9throw
new concurrentmodificationexception();
resize:(重點在此)
jdk1.7中,resize時,index取得時,全部採用重新hash的方式進行了。jdk1.8對這個進行了改善。以前要確定index的時候用的是(e.hash & oldcap-1),是取模取餘,而這裡用到的是(e.hash &oldcap),它有兩種結果,乙個是0,乙個是oldcap,
比如oldcap=8,hash是3,11,19,27時,(e.hash & oldcap)的結果是0,8,0,8,這樣3,19組成新的鍊錶,index為3;而11,27組成新的鍊錶,新分配的index為3+8;
jdk1.7中重寫hash是(e.hash & newcap-1),也就是3,11,19,27對16取餘,也是3,11,3,11,和上面的結果一樣,但是index為3的鍊錶是19,3,index為3+8的鍊錶是
27,11,也就是說1.7中經過resize後資料的順序變成了倒敘,而1.8沒有改變順序。
原理:我們使用的是2次冪的擴充套件(指長度擴為原來2倍),所以,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。看下圖可以明白這句話的意思,n為table的長度,圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴容後key1和key2兩種key確定索引位置的示例,其中hash1是key1對應的雜湊與高位運算結果。
元素在重新計算hash之後,因為n變為2倍,那麼n-1的mask範圍在高位多1bit(紅色),因此新的index就會發生這樣的變化:
因此,我們在擴充hashmap的時候,不需要像jdk1.7的實現那樣重新計算hash,只需要看看原來的hash值新增的那個bit是1還是0就好了,是0的話索引沒變,是1的話索引變成「原索引+oldcap」,可以看看下圖為16擴充為32的resize示意圖:
這個設計確實非常的巧妙,既省去了重新計算hash值的時間,而且同時,由於新增的1bit是0還是1可以認為是隨機的,因此resize的過程,均勻的把之前的衝突的節點分散到新的bucket了。這一塊就是jdk1.8新增的優化點。有一點注意區別,jdk1.7中rehash的時候,舊鍊錶遷移新鍊錶的時候,如果在新錶的陣列索引位置相同,則鍊錶元素會倒置,但是從上圖可以看出,jdk1.8不會倒置。原文:https:
上面的這位博主已經說的十分清楚了,鄙人也就不獻醜了^_^, 詳情可移步原博。
詳細原始碼解析:在這裡引用的是另一博主的(老艮頭--jdk8:hashmap原始碼解析:resize方法)
final node resize()
/** 如果陣列元素個數在正常範圍內,那麼新的陣列容量為老的陣列容量的2倍(左移1位相當於乘以2)
* 如果擴容之後的新容量小於最大容量 並且 老的陣列容量大於等於預設初始化容量(16),那麼新陣列的擴容閥值設定為老閥值的2倍。(老的陣列容量大於16意味著:要麼建構函式指定了乙個大於16的初始化容量值,要麼已經經歷過了至少一次擴容)
*/else
if ((newcap = oldcap << 1) < maximum_capacity &&oldcap >=default_initial_capacity)
newthr = oldthr << 1; //
double threshold}
//ps2
//執行到這個else if 說明老陣列沒有任何元素
//如果老陣列的擴容閥值大於0,那麼設定新陣列的容量為該閥值
//這一步也就意味著構造該map的時候,指定了初始化容量。
else
if (oldthr > 0) //
initial capacity was placed in threshold
newcap =oldthr;
else
//如果擴容閥值為0 (ps2的情況)
if (newthr == 0)
threshold = newthr; //
設定map的擴容閥值為 新的閥值
@suppresswarnings()
//建立新的陣列(對於第一次新增元素,那麼這個陣列就是第乙個陣列;對於存在oldtab的時候,那麼這個陣列就是要需要擴容到的新陣列)
node newtab = (node)new
node[newcap];
table = newtab; //
將該map的table屬性指向到該新陣列
if (oldtab != null)
//如果與運算結果不為0,說明hash值大於老陣列長度(例如hash值為17)
//此時該元素應該放置到新陣列的高位位置上
//例:老陣列長度16,那麼新陣列長度為32,hash為17的應該放置在陣列的第17個位置上,也就是下標為16,那麼下標為16已經屬於高位了,低位是[0-15],高位是[16-31]
else
} while ((e = next) != null
);
if (lotail != null)
if (hitail != null) }}
}}return newtab; //
返回新陣列
}
JDK原始碼之HashMap
部分重要屬性 存放key,value的陣列 transient node table 存放entry的set transient set entryset hashmap的大小 預設16 transient int size 修改次數 transient int modcount 擴擴容閾值capa...
jdk原始碼解析二之HashMap
hashmap的loadfactor為什麼是0.75?主要涉及到泊松分布的概念,猝 乙個bucket空和非空的概率為0.5,通過牛頓二項式等數學計算,得到這個loadfactor的值為log 2 約等於0.693.同回答者所說,可能小於0.75 大於等於log 2 的factor都能提供更好的效能,...
JDK原始碼學習 HashMap
什麼是hash?hash的意思是 雜湊 音譯做 雜湊 輸入乙個任意長度的資料,進過雜湊運算之後,輸出一段固定長度的資料,作為輸入資料的指紋,輸出的結果就是雜湊值。一般來說輸入資料的空間遠遠大於輸出的雜湊值的空間,輸入不同的資料可能會產生相同的雜湊值,所以很難從雜湊值來逆向推出輸入值是什麼。雜湊函式本...