能夠絕不誇張的說,HashMap是容器類中用的最頻繁的一個,而Java也對它進行優化,在jdk1.7及之前,當將相同Hash值的對象以key的身份放到HashMap中,HashMap的性能將由O(1)降低到O(N),因此jdk1.8將相同Hash值的key以紅黑樹的形式進行存儲。數據結構
給個人感覺是,給用戶自由,可是要在限定的範圍內。ide
首先介紹初始容量是什麼,引用Java API中的介紹:性能
HashMap 的實例有兩個參數影響其性能:初始容量 和加載因子。容量 是哈希表中桶的數量,初始容量只是哈希表在建立時的容量。加載因子 是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操做(即重建內部數據結構),從而哈希表將具備大約兩倍的桶數。
也就是說,咱們能夠有遠見知道HashMap中將要存入多少數據,而相應的設置初始容量,減小rehash的次數,由於每次rehash將會對HashMap進行一次重構,影響性能,所以HashMap的構造方法中提供了對初始容量的設置:優化
HashMap(int initialCapacity) :構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。 HashMap(int initialCapacity, float loadFactor) :構造一個帶指定初始容量和加載因子的空 HashMap。
可是HashMap在設計的時候,已經考慮到要rehash,以及根據Hash值在固定數量的桶中查詢數據,加上對數字的移位運算最高效,因此桶的數量被設計爲2的幾回方,可是用戶輸入初始容量是任意的,HashMap是怎麼處理的呢?它是取比輸入值-1大的且最近的2的幾回方的值:this
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
也就是說最終初始容量不必定安裝你輸入的來,就像一開始說的那樣,HashMap給你必定的自由,可是不是絕對的自由。spa
之後寫代碼也能夠這麼作,當一個重要屬性可能影響效率時,不能給使用者太大的自由度,否則用起來慢就很差了。設計
首先簡單說明下列表和紅黑樹,對它們有了基本的認識後才能知道爲何決定將列表轉成紅黑樹:code
也就是,有一個閾值,當超過閾值時纔會將列表轉換成紅黑樹,爲了提升性能HashMap仍是考慮的不少的,主要有三個屬性涉及到列表和紅黑樹轉換:對象
static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64
TREEIFY_THRESHOLD :當一個節點的數量大於此數量的時候,將會將此節點列表轉換成一個樹。blog
UNTREEIFY_THRESHOLD :當已是一個樹的節點,在移除元素的時候,若是移除後小於這個值,則將樹轉換成列表。
MIN_TREEIFY_CAPACITY :當一個節點準備轉換成樹以前,若是HashMap桶的數量小於此,則不進行轉換,而是將HashMap進行擴容。
也就是說爲了一個優化,至關於將HashMap一半的代碼進行了從新,爲了提高當元素都放置到一個桶時性能的降低。可是對於用戶來講是透明的,用戶在使用上徹底感覺不到變化,因此說優化是沒有終點的,這一點我仍是挺佩服他們的。
以前講TreeMap的時候說過,放入TreeMap的key必須具有可比較性,要麼自己實現Comparable接口,要麼傳入key的比較器,由於若是key不能比較大小,就沒辦法構建一棵樹。而咱們在放入HashMap的key時,卻沒有對此有要求,它是怎麼實現的呢。
首先判斷key的類型是不是Comparable,若是是,就經過自身的比較方法進行比較。
若是key不是Comparable,那麼就經過key自己的Hash值進行比較,即使子類重寫了hashCode方法,也會用最原始的,其實是用了System的一個方法:
System.identityHashCode(Object x)
這個方法,返回給定對象的哈希碼,該代碼與默認的方法 hashCode() 返回的代碼同樣,不管給定對象的類是否重寫 hashCode()。
也就是說最終老是能比較出大小,固然若是還同樣,說明key是同樣的,覆蓋便可。
其中判斷key的類型是不是Comparable中有一段代碼以下:
static Class<?> comparableClassFor(Object x) { if (x instanceof Comparable) { Class<?> c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) // bypass checks return c; if ((ts = c.getGenericInterfaces()) != null) { for (int i = 0; i < ts.length; ++i) { if (((t = ts[i]) instanceof ParameterizedType) && ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c return c; } } } return null; }
說實話我一開始沒有想到會這麼複雜,後來我認真研究了一下發現一個類即使實現了Comparable接口,也有可能比較的是其餘類:
class Dog implements Comparable<Object>{ public String name; public Dog(String name) { this.name = name; } @Override public int compareTo(Object o) { // TODO Auto-generated method stub return 0; } }
沒想到判斷的這麼嚴謹,由於印象中不多有類實現Comparable接口,而不去比較本身的,簡單總結它的邏輯:
看完這部分,我想了想TreeMap爲何沒有借鑑HashMap的這種方式,而必須讓key具備比較性,緣由其實很簡單,HashMap的做用就是存儲快速讀取,而TreeMap的額外多了個目的就是排序,若是一個key不具有可比較性,而最終使用了最原始的hashCode,那排序就沒有了意義,還不如使用更高效的HashMap呢。
HashMap作爲最經常使用的容器類,Java已經封裝的足夠好了,而咱們使用的時候若是能作到如下兩點也就能最大化的提升HashMap的性能:
其它暫時沒有遇到什麼問題。