1、Java HashMap的工做原理node
jdk1.7下HashMap數據結構:數組加鏈表,鏈表長度沒有8的限制;算法
jdk1.8 HashMap數據結構:數組+鏈表+紅黑樹;鏈表超過8會轉存爲紅黑樹;數組
1.jdk1.8 中HashMap的put工做原理:緩存
1)、對key作null檢查。若是key是null,會被存儲到table[0],由於null的hash值老是0。安全
2)、判斷當前桶是否爲空,空的就須要初始化(resize 中會判斷是否進行初始化)數據結構
給定的默認容量爲 16,負載因子爲 0.75。Map 在使用過程當中不斷的往裏面存放數據,當數量達到了 16 * 0.75 = 12 就須要將當前 16 的容量進行擴容,而擴容這個過程涉及到 rehash、複製數據等操做,因此很是消耗性能。併發
3)、獲取key的hashCode值(能夠理解爲內存地址位置,如:3254239),並對該32位hashCode值進行高低16位的異或運算;獲得hash值(如:3812);(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);異步
4)、將獲取到的hash值進行一個求模運算(至關於3812%16),獲得該key對應的索引位置;if ((p = tab[i = (n - 1) & hash]) == null);該位置確定不會超過16位;函數
5)、根據當前 key 的 hashcode 定位到具體的桶中並判斷是否爲空,爲空代表沒有 Hash 衝突就直接在當前位置建立一個新桶便可。性能
6)、若是當前桶有值( Hash 衝突),那麼就要比較當前桶中的 key、key 的 hashcode 與寫入的 key 是否相等,相等就賦值給 e,在第 8 步的時候會統一進行賦值及返回。
7)、若是當前桶爲紅黑樹,那就要按照紅黑樹的方式寫入數據。
8)、若是是個鏈表,就須要將當前的 key、value 封裝成一個新節點寫入到當前桶的後面(造成鏈表)。
9)、接着判斷當前鏈表的大小是否大於預設的閾值,大於時就要轉換爲紅黑樹。
10)、若是在遍歷過程當中找到 key 相同時直接退出遍歷。
11)、若是 e != null 就至關於存在相同的 key,那就須要將值覆蓋。
12)、最後判斷是否須要進行擴容。
2.jdk1.8 中HashMap的get工做原理:
1)、計算hash部分跟put相同;
2)、首先將 key hash 以後取得所定位的桶。
3)、若是桶爲空則直接返回 null 。
4)、不然判斷桶的第一個位置(有多是鏈表、紅黑樹)的 key 是否爲查詢的 key,是就直接返回 value。
5)、若是第一個不匹配,則判斷它的下一個是紅黑樹仍是鏈表。
6)、紅黑樹就按照樹的查找方式返回值。
7)、否則就按照鏈表的方式遍歷匹配返回值。
問題1:HashMap爲什麼要進行hash計算?hash函數是怎麼運算的?
儘可能讓node落點分佈均勻,減小碰撞的一個機率,若是碰撞機率高了,就勢必致使數組下標下的鏈表長度太長;
運算方式:將獲取到的32位hash值,分紅高16位和低16位,再將高16位移到低16位進行異或運算;
32位:3254239,經過異或運算後,3812,
table[3812]越界,再作個求模運算3812%16=,必定不會超過16位;hash%n===(n-1)&hash,速度快,效率高;
問題2:數組擴容,2倍擴容;爲何是2的N次冪;就是由於hash算法這(n-1)&hash;要符合這算法;必須是這樣;
resize()方法中的擴容:newThr = oldThr << 1; // double threshold
問題3:可是 HashMap 原有的問題也都存在,好比在併發場景下使用時容易出現死循環。
HashMap 擴容的時候會調用 resize() 方法,就是這裏的併發操做容易在一個桶上造成環形鏈表;這樣當獲取一個不存在的 key 時,計算出的 index 正好是環形鏈表的下標就會出現死循環。
2、Hashtable、LinkedHashMap、TreeMap、SortedMap、WeakHashMap、IdentityHashMap、ConcurrentHashMap的區別:
(1) HashMap與HashTable的區別:
a.Hashtable中的對象是線程安全的。而HashMap則是異步的,所以HashMap中的對象並非線程安全的。由於同步的要求會影響執行的效率,因此若是你不須要線程安全的集合那麼使用
HashMap是一個很好的選擇,這樣能夠避免因爲同步帶來的沒必要要的性能開銷,從而提升效率。
b.值:HashMap可讓你將空值做爲一個表的條目的key或value,可是Hashtable是不能放入空值的。HashMap最多隻有一個key值爲null,但能夠有無數多個value值爲null。
注意:
一、用做key的對象必須實現hashCode和equals方法。
二、不能保證其中的鍵值對的順序
三、儘可能不要使用可變對象做爲它們的key值。
(2) LinkedHashMap:
它的父類是HashMap,使用雙向鏈表來維護鍵值對的次序,迭代順序與鍵值對的插入順序保持一致。LinkedHashMap須要維護元素的插入順序,插入性能略低於HashMap,但在迭代訪問元
素時有很好的性能,由於它是以鏈表來維護內部順序。
(3) TreeMap和SortedMap:
Map接口派生了一個SortMap子接口,SortMap的實現類爲TreeMap。TreeMap也是基於紅黑樹對全部的key進行排序,有兩種排序方式:天然排序和定製排序。HashMap一般比TreeMap快一點(樹
和哈希表的數據結構使然),建議多使用HashMap,在須要排序的Map時候才用TreeMap。
(4) WeakHashMap:
WeakHashMap與HashMap的用法基本相同,區別在於:後者的key保留對象的強引用,即只要HashMap對象不被銷燬,其對象全部key所引用的對象不會被垃圾回收;WeakHashMap適合短期內就過時的緩存時最好使用weakHashMap,它包含了一個自動調用的方法expungeStaleEntries,這樣就會在值被引用後直接執行這個隱含的方法,將不用的鍵清除掉。
(5) IdentityHashMap類:
IdentityHashMap與HashMap基本類似,只是當兩個key嚴格相等時,即key1==key2時,它才認爲兩個key是相等的 。IdentityHashMap也容許使用null,但不保證鍵值對之間的順序。
(6) EnumMap類:
一、EnumMap中全部key都必須是單個枚舉類的枚舉值,建立EnumMap時必須顯示或隱式指定它對應的枚舉類。
二、EnumMap根據key的天然順序,即枚舉值在枚舉類中定義的順序,來維護鍵值對的次序。
三、EnumMap不容許使用null做爲key值,但value能夠。
(7) ConcurrentHashMap:
1.ConcurrentHashMap對整個桶數組進行了分段,而HashMap則沒有
2.ConcurrentHashMap在每個分段上都用鎖進行保護,從而讓鎖的粒度更精細一些,併發性能更好,而HashMap沒有鎖機制,不是線程安全的。
ConcurrentHashMap如何進行擴容的?
當往hashMap中成功插入一個key/value節點時,有可能觸發擴容動做:
一、若是新增節點以後,所在鏈表的元素個數達到了閾值 8,則會調用treeifyBin
方法把鏈表轉換成紅黑樹,不過在結構轉換以前,會對數組長度進行判斷
若是數組長度n小於閾值MIN_TREEIFY_CAPACITY
,默認是64,則會調用tryPresize
方法把數組長度擴大到原來的兩倍,並觸發transfer
方法,從新調整節點的位置。
二、新增節點以後,會調用addCount方法記錄元素個數,並檢查是否須要進行擴容,當數組元素個數達到閾值時,會觸發transfer方法,從新調整節點的位置。
3、紅黑樹的理解? 紅黑樹是一種自平衡二叉查找樹,紅黑樹是一種頗有意思的平衡檢索樹;每次插入的時候都要進行計算,保證二叉樹的平衡;若是有2的N次方數據量級,查詢的時候只須要查詢N次便可。 咱們對任何有效的紅黑樹加以以下增補要求: 1.節點是紅色或黑色。 2.根是黑色。 3.全部葉子(外部節點)都是黑色。 4.每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點) 5.從每一個葉子到根的全部路徑都包含相同數目的黑色節點。 這些約束強制了紅黑樹的關鍵屬性: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。