jdk1.8 HashMap 實現 陣列+連結串列/紅黑樹(預設桶中長度大於8時)
轉載至 http://www.cnblogs.com/leesf456/p/5242233.html
一、前言
在分析jdk1.8後的HashMap原始碼時,發現網上好多分析都是基於之前的jdk,而Java8的HashMap對之前做了較大的優化,其中最重要的一個優化就是桶中的元素不再唯一按照連結串列組合,也可以使用紅黑樹進行儲存,總之,目標只有一個,那就是在安全和功能性完備的情況下讓其速度更快,提升效能。好~下面就開始分析原始碼。
二、HashMap資料結構
說明:上圖很形象的展示了HashMap的資料結構(陣列+連結串列+紅黑樹),桶中的結構可能是連結串列,也可能是紅黑樹,紅黑樹的引入是為了提高效率。所以可見,在分析原始碼的時候我們不知不覺就溫習了資料結構的知識點,一舉兩得。
三、HashMap原始碼分析
3.1 類的繼承關係
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
可以看到HashMap繼承自父類(AbstractMap),實現了Map、Cloneable、Serializable介面。其中,Map介面定義了一組通用的操作;Cloneable介面則表示可以進行拷貝,在HashMap中,實現的是淺層次拷貝,即對拷貝物件的改變會影響被拷貝的物件;Serializable介面表示HashMap實現了序列化,即可以將HashMap物件儲存至本地,之後可以恢復狀態。
3.2 類的屬性
View Code說明:類的資料成員很重要,以上也解釋得很詳細了,其中有一個引數MIN_TREEIFY_CAPACITY,筆者暫時還不是太清楚,有讀者知道的話歡迎指導。
3.3 類的建構函式
1. HashMap(int, float)型建構函式
View Code說明:tableSizeFor(initialCapacity)返回大於等於initialCapacity的最小的二次冪數值。
View Code說明:>>> 操作符表示無符號右移,高位取0。
2. HashMap(int)型建構函式。
View Code3. HashMap()型建構函式。
View Code4. HashMap(Map<? extends K>)型建構函式。
View Code說明:putMapEntries(Map<? extends K, ? extends V> m, boolean evict)函式將m的所有元素存入本HashMap例項中。
View Code3.4 重要函式分析
1. putVal函式
View Code說明:HashMap並沒有直接提供putVal介面給使用者呼叫,而是提供的put函式,而put函式就是通過putVal來插入元素的。
2. getNode函式
View Code說明:HashMap並沒有直接提供getNode介面給使用者呼叫,而是提供的get函式,而get函式就是通過getNode來取得元素的。
3. resize函式
View Code說明:進行擴容,會伴隨著一次重新hash分配,並且會遍歷hash表中所有的元素,是非常耗時的。在編寫程式中,要儘量避免resize。
在resize前和resize後的元素佈局如下
說明:上圖只是針對了陣列下標為2的桶中的各個元素在擴容後的分配佈局,其他各個桶中的元素佈局可以以此類推。
四、針對HashMap的思考
4.1. 關於擴容的思考
從putVal原始碼中我們可以知道,當插入一個元素的時候size就加1,若size大於threshold的時候,就會進行擴容。假設我們的capacity大小為32,loadFator為0.75,則threshold為24 = 32 * 0.75,此時,插入了25個元素,並且插入的這25個元素都在同一個桶中,桶中的資料結構為紅黑樹,則還有31個桶是空的,也會進行擴容處理,其實,此時,還有31個桶是空的,好像似乎不需要進行擴容處理,但是是需要擴容處理的,因為此時我們的capacity大小可能不適當。我們前面知道,擴容處理會遍歷所有的元素,時間複雜度很高;前面我們還知道,經過一次擴容處理後,元素會更加均勻的分佈在各個桶中,會提升訪問效率。所以,說盡量避免進行擴容處理,也就意味著,遍歷元素所帶來的壞處大於元素在桶中均勻分佈所帶來的好處。如果有讀者有不同意見,也歡迎討論~
五、總結
至此,HashMap的原始碼就分析到這裡了,其中理解了其中的核心函式和資料結構,那麼理解HashMap的原始碼就不困難了。當然,此次分析中還有一些知識點沒有涉及到,如紅黑樹、序列化、拷貝等,以後有機會會進行詳細的說明和講解,謝謝各位園友的觀看~