來總結一下HashMap的原理node
1.HashMap當中有一個內部類,它叫Node,而後這個Node呢,它實際上是實現了Map.Entry接口,這個接口當中有幾個抽象的方法和幾個具體的方法。其中Map.Entry<K,V>是一個泛型的元組。算法
2.Map.Entry接口中有以下抽象方法:api
3.Node的私有變量以下:數組
其中HashMap的核心是hashcode的生成算法,hashCode的生成算法以下:ide
Objects.hashCode(key) ^ Objects.hashCode(value);
它是先經過獲得Key和value的hashcode,而後對2個值進行異或操做後獲得的值。單元測試
其中Object.hashCode是一個native的方法。測試
public native int hashCode();
其中Node的equals方法,傳入的對象是object,只有當object的類型是map.entry而且,當前對象的key和value都和傳入的key,value一致,那樣纔會返回相等。this
下面的這個方法,是計算hash值的方法。它是經過key去計算,而後把拿到的hashcode和它右移16位的結果進行異或操做,具體回頭再看爲何,我也不知道。idea
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
hashMap裏面還有一個entrySet的成員變量,它是一個set 的集合,這裏面的transient關鍵字不太懂,回頭再看看。spa
transient Set<Map.Entry<K,V>> entrySet;
HashMap裏面有一個很是重要的方法,叫作putVal()方法。這算是裏面最核心的一個方法了,弄懂了這個方法,80%的HashMap相關的知識都能弄懂了
首先是有2個Node的聲明,一個是tab,一個是p.
Node<K,V>[] tab; Node<K,V> p; int n, i;
下面咱們來解析一下PutVal方法,若是table爲空,或者table的長度爲0,重置table的長度。
if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
首先是下面的代碼會利用到上面的代碼,n得出了一個結果,那就是resize()後的結果,下面的n-1就是「」最後「」一個元素
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
下面的代碼就是上面的tabl[x]裏面的邏輯,這裏面用到了按位與的一個運算:爲何要這麼作?不知道。
來作一個小小的補充,這裏要先複習一下按位與的結果操做,何時獲取什麼值,若是hash是一個負數,那又是什麼狀況呢?
(n - 1) & hash
我猜測的是,若是「」找到的「」元素爲null,那麼新建一個node元素。而且這個node元素的next爲空。不然執行else裏面的邏輯。
----------------------------------------我是分割線---------------------------------------------
首先putVal方法會去計算這個key的hash值。
首先我以爲要明白hash算法的真諦,網上找的這句話,說得不錯:要找到散列爲同一個值的兩個不一樣的輸入,在計算上是不可能的,因此數據的哈希值能夠檢驗數據的完整性。
當第一次進入putval方法的時候,table是空的,因此確定要進行一個resize操做,不光是table,連threshold都是0,全部的東西都未能初始化的狀況下,這個時候,應該進入以下邏輯:
newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
這個時候,newCapicity的話就變成了初始值16,newThr變成了初始化的Threhold,若是是這種狀況下的話,就會新建一個長度爲16的Node<K,V>[]數組,最後返回newTable,注意,resize操做的返回對象。
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
在下面的例子中,hash值是一個很是大的值,換算成2進制,它是32位長度的一個2進制,用按位與的操做是最快的,由於計算機內部結構就是二進制的。
if ((p = tab[i = (n - 1) & hash]) == null)
注意!!!它先填充的是tab[10]的內容,也就是說,並無從0開始填充,這是違背咱們直覺的一件事情。
而後讓modCount++,
最後,若是負載因子小於size,那麼,hashmap會自動擴容。
if (++size > threshold) resize();
隨後執行afterNodeInsertion方法,這個方法在Hashmap當中是一個空的方法,API裏面介紹的是爲LinkedHashMap所用,因此這裏再也不作討論。還有注意下,若是是新建的hashMap第一次putval,那麼它的返回值爲null.
要注意一點,新建一個HashMap,它並非獨立存在的,在你把你的key添加進去以前,它還會添加很是多的其餘的KEY,也就是咱們所說的:系統路徑,因此最後得出的結果就是,若是你是一個新的HashMap,那麼,你添加了一個KEY,確定這裏面不止一個KEY。
你們能夠觀察到,當SIZE=13的時候,
實際上是自動進入了resize這個方法的,你看我斷點都進來了。這就證實了hashmap的自動擴容機制。
那麼爲何會有這麼多的Node被添加進來呢?緣由只有一個,就是咱們用idea啓動項目的時候,一些類實際上是用到了HashMap的,它優於咱們調試的時候進入的HashMap,因此剛纔你們纔會看到那麼多的節點被添加到hashMap當中去。
當我把在putVal上的斷點去掉之後,就進入了以下代碼塊,驗證了個人猜測。
另外還有一個頗有趣現象,我用單元測試,新建了一個HashMap,結果。。。你發現沒有,jdk裏面已經填充了4項了,原來,咱們認爲的Hashmap,有多少項,就add多少項的觀點實際上是錯誤的!!!
下面咱們再來看看以下代碼,傳入的hash和以前的hash進行對比,這裏面可能你們有一些迷糊,固然包括我也看不懂,不過從這裏能夠獲取一個很是重要的信息,這麼作的方式就是爲了不一個hashmap鍾可能出現「相同」的hash對象,我是這麼理解的,若是有高人,能夠來解釋下爲何這樣。
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;
下面的代碼也很明白了,若是不是上面都 ,那麼若是是樹節點,那麼就執行下面的邏輯,此處再也不深究。
else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
下面的代碼的含義是,若是p.next爲空的話,那麼新建一個節點,並追加到尾部,這種狀況,就是當p指向最後一個節點的時候纔會出現的狀況。
for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; }
後面的代碼有點感受太難啃了,先暫時就這樣吧,今天的HashMap分析得還不太完整,而且不太合理,但願你們能多提寶貴建議。