HashMap源碼解析

對於HashMap,若是是java程序員,那麼定然不會陌生,對於HashMap,應該說是最經常使用的一種Map結構了,一樣在面試當中也會屢屢被提問到,常見的幾種題目:java

  • HashMap的默認容量?
  • HashMap是如何擴容的?
  • HashMap的數組大小爲何必定是2的冪?
  • HashMap爲何是線程不安全的?
  • Java7到Java8作了哪些改進?爲何?

由於重要,因此我也就學習源碼,而且將學習心得記錄下來,與你們一塊兒學習。程序員

首先 再看HashMap以前,咱們來簡單回顧一下哈希表面試

哈希表是由一些基於哈希值的桶和鏈表所構成的。哈希桶就是能夠快速檢索的數據結構,舉個例子 若是要尋找電話本的人的聯繫方式,咱們能夠利用拼音的首字母快速定位到這個聯繫人,存放這些字母的就叫哈希桶。本質上,哈希桶就是 將一個元素映射成一個能夠快速檢索的哈希值。算法

哈希桶加上數組就構成了哈希表,數組的好處是隨機尋址的速度與長度無關,時間複雜度是O(1),可是哈希表最大的缺點是會發生碰撞,若是多個元素的哈希值是相同的,那麼咱們就說哈希值發生了碰撞,爲了解決這個問題,咱們將數組換成了鏈表,找到哈希值時,經過哈希桶裏能夠精確的找到所要查找的元素。平均差找時間都是O(1)。在java世界中,咱們用來表達哈希表的數據結構就是HashMap。數組

image.png

哈希桶的實現是由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會有不少問題:

  1. 會碰到死鎖
  2. CVE-2011-1858  TOMCAT郵件組的討論

對於死鎖問題,其實多數是程序員本身的問題,由於HashMap自己就不是線程安全的,當在多個線程中使用hashmap的時候,就必須給他加入同步的環境.

對於TOMCAT郵件組的討論,是說一個安全問題,若是咱們的多個元素映射成同一個哈希值時,會把哈希表退化成一個鏈表,而鏈表的查詢的時間複雜度的o(n),那麼這個查詢速度是很可怕的,若是是被黑客利用精心構造的成千上萬個元素,具備一樣的哈希值,就能夠引發Dos攻擊,針對這個問題,sun公司提供了一個小補丁,就是若是檢查到元素是String的話那麼就該用另一種不一樣於默認的hash算法來去避免潛在的危險。

此時,針對於這兩個問題,咱們迎來了Java8以後的HashMap

Java8對於以前的HashMap以前作了哪些改進呢?

  • 由以前數組/鏈表--->數組/紅黑樹
  • 擴容時插入順序的改進
  • 函數方法
    • foreach
    • compute系列
  • Map的新API
    • merge
    • replace

在Java8的源碼中,爲何變成樹的閾值是8?咱們能夠看到對於紅黑樹的容量服從參數爲0.5的泊松分佈,大於8的桶中的容量是10萬分之一,不易發生碰撞。

在進行put操做時,若是超過閾值,就把桶變爲紅黑樹,若是沒超過,仍是用鏈表來實現。

在進行擴容操做時,掌握了java7時的操做,咱們能夠想到,擴容爲二倍,要麼索引值和以前同樣,要麼就是最高位是在以前索引值的基礎上再加一個一,就至關因而把原先的變成了兩個鏈表,一個是高位的鏈表,一個是低位的鏈表,在把它賦值給新的桶中去。從而緩解了死鎖問題(生成環的問題)。

相關文章
相關標籤/搜索