對於HashMap,若是是java程序員,那麼定然不會陌生,對於HashMap,應該說是最經常使用的一種Map結構了,一樣在面試當中也會屢屢被提問到,常見的幾種題目:java
由於重要,因此我也就學習源碼,而且將學習心得記錄下來,與你們一塊兒學習。程序員
首先 再看HashMap以前,咱們來簡單回顧一下哈希表面試
哈希表是由一些基於哈希值的桶和鏈表所構成的。哈希桶就是能夠快速檢索的數據結構,舉個例子 若是要尋找電話本的人的聯繫方式,咱們能夠利用拼音的首字母快速定位到這個聯繫人,存放這些字母的就叫哈希桶。本質上,哈希桶就是 將一個元素映射成一個能夠快速檢索的哈希值。算法
哈希桶加上數組就構成了哈希表,數組的好處是隨機尋址的速度與長度無關,時間複雜度是O(1),可是哈希表最大的缺點是會發生碰撞,若是多個元素的哈希值是相同的,那麼咱們就說哈希值發生了碰撞,爲了解決這個問題,咱們將數組換成了鏈表,找到哈希值時,經過哈希桶裏能夠精確的找到所要查找的元素。平均差找時間都是O(1)。在java世界中,咱們用來表達哈希表的數據結構就是HashMap。數組
哈希桶的實現是由hashcode實現的int hashcode 底下有對象:object1,object2....安全
咱們先來看java7的HashMap。數據結構
經典的數據結構:數組加鏈表。函數
經過查看源碼 咱們能夠知曉HashMap的默認容量爲你16,若是改變負載因子 而且將初始值設爲17 結果會怎麼樣麼?再次點開源碼,咱們會發現他會向上取整爲2的冪也就是變成了32.以後咱們能夠看到負載因子是0.75.學習
這裏有一個問題,就是桶的初始化容量是16個,而咱們的HashCode值是-2^31~2^31-1,42億個數。那麼咱們怎麼從任意一個int變成0~n-1。聰明的你確定想到了取餘這個想法,可是在這個方法有兩個缺點:spa
那麼咱們點開源碼發現,爲了尋找要插入的元素的索引值,作了一個鬼畜的操做:hash&(length-1),作這樣一個位運算,那麼這操做正好能夠解釋爲何咱們須要輸進去的HashMap的初始容量是2的冪,咱們能夠看出hash&(2^n-1),最後的下標就是Hashmap對應的2^n-1的個數下標,之因此把容量定爲2的冪,就是由於讓2^n-1位運算時拿到的值所有是1,這樣作與運算就能夠快速找到下標而且分佈仍是均勻的(妙啊)。
解釋完這個問題 ,咱們來看看是怎麼擴容的,根據源碼,我麼能夠看到,當所須要的容量超過原始容量*負載因子(0.75)時,就須要進行擴容,擴容的大小是以前的兩倍。而在擴容的過程當中就包含了一個rehash的操做。那麼在擴容的時候,會進行transfer也就是將以前的數據進行遷移的操做,遍歷全部的元素,而且從新計算哈希值,找到在新表裏的索引,把它放進去。這是一個巨坑!!!爲了不高位不一樣,低位相同,進行了高位與低位的異或操做。
在Java7中的HashMap會有不少問題:
對於死鎖問題,其實多數是程序員本身的問題,由於HashMap自己就不是線程安全的,當在多個線程中使用hashmap的時候,就必須給他加入同步的環境.
對於TOMCAT郵件組的討論,是說一個安全問題,若是咱們的多個元素映射成同一個哈希值時,會把哈希表退化成一個鏈表,而鏈表的查詢的時間複雜度的o(n),那麼這個查詢速度是很可怕的,若是是被黑客利用精心構造的成千上萬個元素,具備一樣的哈希值,就能夠引發Dos攻擊,針對這個問題,sun公司提供了一個小補丁,就是若是檢查到元素是String的話那麼就該用另一種不一樣於默認的hash算法來去避免潛在的危險。
Java8對於以前的HashMap以前作了哪些改進呢?
在Java8的源碼中,爲何變成樹的閾值是8?咱們能夠看到對於紅黑樹的容量服從參數爲0.5的泊松分佈,大於8的桶中的容量是10萬分之一,不易發生碰撞。
在進行put操做時,若是超過閾值,就把桶變爲紅黑樹,若是沒超過,仍是用鏈表來實現。
在進行擴容操做時,掌握了java7時的操做,咱們能夠想到,擴容爲二倍,要麼索引值和以前同樣,要麼就是最高位是在以前索引值的基礎上再加一個一,就至關因而把原先的變成了兩個鏈表,一個是高位的鏈表,一個是低位的鏈表,在把它賦值給新的桶中去。從而緩解了死鎖問題(生成環的問題)。