1. 程式人生 > >JavaSE_HashMap 原始碼詳解 put, get 方法

JavaSE_HashMap 原始碼詳解 put, get 方法

參考文章:

java 8 Hashmap深入解析 —— put get 方法原始碼

       一直以來我是對讀原始碼很不理解的,直到昨天去解讀了 部分的 HashMap 原始碼,發現原始碼中有很多精巧的設計。體現了很多語言層次深入的東西。

  對於普通的程式設計師,可能僅僅能說出HashMap執行緒不安全,允許key、value為null,以及不要求執行緒安全時,效率上比HashTable要快一些。

    稍微好一些的,會對具體實現有過大概瞭解,能說出HashMap由陣列+連結串列+RBT實現,並瞭解HashMap的擴容機制。

    但如果你真的有一個刨根問題的熱情,那麼你肯定會想知道具體是如何一步步實現的。HashMap的原始碼一共2000多行,很難在這裡每一句都說明,但這篇文章會讓你透徹的理解到我們平時常用的幾個操作下,HashMap是如何工作的。

   要先提一下的是,我看過很多講解HashMap原理的文章,有一些講的非常好,但這些文章習慣於把原始碼和邏輯分析分開,導致出現了大段的文字講解程式碼,閱讀起來有些吃力和枯燥。所以我想嘗試另一種風格,將更多的內容寫進註釋裡,可能看起來有些囉嗦,但對於一些新手的理解,應該會有好的效果。

好了,開始進入原始碼解讀。

HashMap結構

   首先是瞭解HashMap的幾個核心成員變數(以下均為jdk原始碼):

transient Node<K,V>[] table;                //HashMap的雜湊桶陣列,非常重要的儲存結構,用於存放表示鍵值對資料的Node元素。

transient Set<Map.Entry<K,V>> entrySet;    //HashMap將資料轉換成set的另一種儲存形式,這個變數主要用於迭代功能。

transient int size;                        //HashMap中實際存在的Node數量,注意這個數量不等於table的長度,甚至可能大於它,因為在table的每個節點上是一個連結串列(或RBT)結構,可能不止有一個Node元素存在。

transient int modCount;                    //HashMap的資料被修改的次數,這個變數用於迭代過程中的Fail-Fast機制,其存在的意義在於保證發生了執行緒安全問題時,能及時的發現(操作前備份的count和當前modCount不相等)並丟擲異常終止操作。

int threshold;                            //HashMap的擴容閾值,在HashMap中儲存的Node鍵值對超過這個數量時,自動擴容容量為原來的二倍。

final float loadFactor;                   //HashMap的負載因子,可計算出當前table長度下的擴容閾值:threshold = loadFactor * table.length。

    那麼這些變數的預設值都是多少呢?我們再看一下HashMap定義的一些常量:

//預設的初始容量為16,必須是2的冪次
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

//最大容量即2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;

//預設載入因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//當put一個元素時,其連結串列長度達到8時將連結串列轉換為紅黑樹
static final int TREEIFY_THRESHOLD = 8;

//連結串列長度小於6時,解散紅黑樹
static final int UNTREEIFY_THRESHOLD = 6;

//預設的最小的擴容量64,為避免重新擴容衝突,至少為4 * TREEIFY_THRESHOLD=32,即預設初始容量的2倍
static final int MIN_TREEIFY_CAPACITY = 64;

  其次 HashMap的底層實現是基於一個Node的陣列,那麼Node是什麼呢?在HashMap的內部可以看見定義了這樣一個內部類:

Node類 :

static class Node<K,V> implements Map.Entry<K,V> {
  final int hash;
  final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

       除此之外,在put 的過程中還用到了 TreeNode 類,我們看下 treenode 的原始碼:

/* ------------------------------------------------------------ */
// Tree bins

/**
 * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
 * extends Node) so can be used as extension of either regular or
 * linked node.
 */
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
	TreeNode<K,V> parent;  // red-black tree links
	TreeNode<K,V> left;
	TreeNode<K,V> right;
	TreeNode<K,V> prev;    // needed to unlink next upon deletion
	boolean red;
	TreeNode(int hash, K key, V val, Node<K,V> next) {
		super(hash, key, val, next);
	}

	/**
	 * Returns root of tree containing this node.
	 */
	final TreeNode<K,V> root() {
		for (TreeNode<K,V> r = this, p;;) {
			if ((p = r.parent) == null)
				return r;
			r = p;
		}
	}

	/**
	 * Ensures that the given root is the first node of its bin.
	 */
	static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
		int n;
		if (root != null && tab != null && (n = tab.length) > 0) {
			int index = (n - 1) & root.hash;
			TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
			if (root != first) {
				Node<K,V> rn;
				tab[index] = root;
				TreeNode<K,V> rp = root.prev;
				if ((rn = root.next) != null)
					((TreeNode<K,V>)rn).prev = rp;
				if (rp != null)
					rp.next = rn;
				if (first != null)
					first.prev = root;
				root.next = first;
				root.prev = null;
			}
			assert checkInvariants(root);
		}
	}

	/**
	 * Finds the node starting at root p with the given hash and key.
	 * The kc argument caches comparableClassFor(key) upon first use
	 * comparing keys.
	 */
	final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
		TreeNode<K,V> p = this;
		do {
			int ph, dir; K pk;
			TreeNode<K,V> pl = p.left, pr = p.right, q;
			if ((ph = p.hash) > h)
				p = pl;
			else if (ph < h)
				p = pr;
			else if ((pk = p.key) == k || (k != null && k.equals(pk)))
				return p;
			else if (pl == null)
				p = pr;
			else if (pr == null)
				p = pl;
			else if ((kc != null ||
					  (kc = comparableClassFor(k)) != null) &&
					 (dir = compareComparables(kc, k, pk)) != 0)
				p = (dir < 0) ? pl : pr;
			else if ((q = pr.find(h, k, kc)) != null)
				return q;
			else
				p = pl;
		} while (p != null);
		return null;
	}

	/**
	 * Calls find for root node.
	 */
	final TreeNode<K,V> getTreeNode(int h, Object k) {
		return ((parent != null) ? root() : this).find(h, k, null);
	}

	/**
	 * Tie-breaking utility for ordering insertions when equal
	 * hashCodes and non-comparable. We don't require a total
	 * order, just a consistent insertion rule to maintain
	 * equivalence across rebalancings. Tie-breaking further than
	 * necessary simplifies testing a bit.
	 */
	static int tieBreakOrder(Object a, Object b) {
		int d;
		if (a == null || b == null ||
			(d = a.getClass().getName().
			 compareTo(b.getClass().getName())) == 0)
			d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
				 -1 : 1);
		return d;
	}

	/**
	 * Forms tree of the nodes linked from this node.
	 * @return root of tree
	 */
	final void treeify(Node<K,V>[] tab) {
		TreeNode<K,V> root = null;
		for (TreeNode<K,V> x = this, next; x != null; x = next) {
			next = (TreeNode<K,V>)x.next;
			x.left = x.right = null;
			if (root == null) {
				x.parent = null;
				x.red = false;
				root = x;
			}
			else {
				K k = x.key;
				int h = x.hash;
				Class<?> kc = null;
				for (TreeNode<K,V> p = root;;) {
					int dir, ph;
					K pk = p.key;
					if ((ph = p.hash) > h)
						dir = -1;
					else if (ph < h)
						dir = 1;
					else if ((kc == null &&
							  (kc = comparableClassFor(k)) == null) ||
							 (dir = compareComparables(kc, k, pk)) == 0)
						dir = tieBreakOrder(k, pk);

					TreeNode<K,V> xp = p;
					if ((p = (dir <= 0) ? p.left : p.right) == null) {
						x.parent = xp;
						if (dir <= 0)
							xp.left = x;
						else
							xp.right = x;
						root = balanceInsertion(root, x);
						break;
					}
				}
			}
		}
		moveRootToFront(tab, root);
	}

	/**
	 * Returns a list of non-TreeNodes replacing those linked from
	 * this node.
	 */
	final Node<K,V> untreeify(HashMap<K,V> map) {
		Node<K,V> hd = null, tl = null;
		for (Node<K,V> q = this; q != null; q = q.next) {
			Node<K,V> p = map.replacementNode(q, null);
			if (tl == null)
				hd = p;
			else
				tl.next = p;
			tl = p;
		}
		return hd;
	}

	/**
	 * Tree version of putVal.
	 */
	final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
								   int h, K k, V v) {
		Class<?> kc = null;
		boolean searched = false;
		TreeNode<K,V> root = (parent != null) ? root() : this;
		for (TreeNode<K,V> p = root;;) {
			int dir, ph; K pk;
			if ((ph = p.hash) > h)
				dir = -1;
			else if (ph < h)
				dir = 1;
			else if ((pk = p.key) == k || (k != null && k.equals(pk)))
				return p;
			else if ((kc == null &&
					  (kc = comparableClassFor(k)) == null) ||
					 (dir = compareComparables(kc, k, pk)) == 0) {
				if (!searched) {
					TreeNode<K,V> q, ch;
					searched = true;
					if (((ch = p.left) != null &&
						 (q = ch.find(h, k, kc)) != null) ||
						((ch = p.right) != null &&
						 (q = ch.find(h, k, kc)) != null))
						return q;
				}
				dir = tieBreakOrder(k, pk);
			}

			TreeNode<K,V> xp = p;
			if ((p = (dir <= 0) ? p.left : p.right) == null) {
				Node<K,V> xpn = xp.next;
				TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
				if (dir <= 0)
					xp.left = x;
				else
					xp.right = x;
				xp.next = x;
				x.parent = x.prev = xp;
				if (xpn != null)
					((TreeNode<K,V>)xpn).prev = x;
				moveRootToFront(tab, balanceInsertion(root, x));
				return null;
			}
		}
	}

	/**
	 * Removes the given node, that must be present before this call.
	 * This is messier than typical red-black deletion code because we
	 * cannot swap the contents of an interior node with a leaf
	 * successor that is pinned by "next" pointers that are accessible
	 * independently during traversal. So instead we swap the tree
	 * linkages. If the current tree appears to have too few nodes,
	 * the bin is converted back to a plain bin. (The test triggers
	 * somewhere between 2 and 6 nodes, depending on tree structure).
	 */
	final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
							  boolean movable) {
		int n;
		if (tab == null || (n = tab.length) == 0)
			return;
		int index = (n - 1) & hash;
		TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
		TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
		if (pred == null)
			tab[index] = first = succ;
		else
			pred.next = succ;
		if (succ != null)
			succ.prev = pred;
		if (first == null)
			return;
		if (root.parent != null)
			root = root.root();
		if (root == null || root.right == null ||
			(rl = root.left) == null || rl.left == null) {
			tab[index] = first.untreeify(map);  // too small
			return;
		}
		TreeNode<K,V> p = this, pl = left, pr = right, replacement;
		if (pl != null && pr != null) {
			TreeNode<K,V> s = pr, sl;
			while ((sl = s.left) != null) // find successor
				s = sl;
			boolean c = s.red; s.red = p.red; p.red = c; // swap colors
			TreeNode<K,V> sr = s.right;
			TreeNode<K,V> pp = p.parent;
			if (s == pr) { // p was s's direct parent
				p.parent = s;
				s.right = p;
			}
			else {
				TreeNode<K,V> sp = s.parent;
				if ((p.parent = sp) != null) {
					if (s == sp.left)
						sp.left = p;
					else
						sp.right = p;
				}
				if ((s.right = pr) != null)
					pr.parent = s;
			}
			p.left = null;
			if ((p.right = sr) != null)
				sr.parent = p;
			if ((s.left = pl) != null)
				pl.parent = s;
			if ((s.parent = pp) == null)
				root = s;
			else if (p == pp.left)
				pp.left = s;
			else
				pp.right = s;
			if (sr != null)
				replacement = sr;
			else
				replacement = p;
		}
		else if (pl != null)
			replacement = pl;
		else if (pr != null)
			replacement = pr;
		else
			replacement = p;
		if (replacement != p) {
			TreeNode<K,V> pp = replacement.parent = p.parent;
			if (pp == null)
				root = replacement;
			else if (p == pp.left)
				pp.left = replacement;
			else
				pp.right = replacement;
			p.left = p.right = p.parent = null;
		}

		TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

		if (replacement == p) {  // detach
			TreeNode<K,V> pp = p.parent;
			p.parent = null;
			if (pp != null) {
				if (p == pp.left)
					pp.left = null;
				else if (p == pp.right)
					pp.right = null;
			}
		}
		if (movable)
			moveRootToFront(tab, r);
	}

	/**
	 * Splits nodes in a tree bin into lower and upper tree bins,
	 * or untreeifies if now too small. Called only from resize;
	 * see above discussion about split bits and indices.
	 *
	 * @param map the map
	 * @param tab the table for recording bin heads
	 * @param index the index of the table being split
	 * @param bit the bit of hash to split on
	 */
	final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
		TreeNode<K,V> b = this;
		// Relink into lo and hi lists, preserving order
		TreeNode<K,V> loHead = null, loTail = null;
		TreeNode<K,V> hiHead = null, hiTail = null;
		int lc = 0, hc = 0;
		for (TreeNode<K,V> e = b, next; e != null; e = next) {
			next = (TreeNode<K,V>)e.next;
			e.next = null;
			if ((e.hash & bit) == 0) {
				if ((e.prev = loTail) == null)
					loHead = e;
				else
					loTail.next = e;
				loTail = e;
				++lc;
			}
			else {
				if ((e.prev = hiTail) == null)
					hiHead = e;
				else
					hiTail.next = e;
				hiTail = e;
				++hc;
			}
		}

		if (loHead != null) {
			if (lc <= UNTREEIFY_THRESHOLD)
				tab[index] = loHead.untreeify(map);
			else {
				tab[index] = loHead;
				if (hiHead != null) // (else is already treeified)
					loHead.treeify(tab);
			}
		}
		if (hiHead != null) {
			if (hc <= UNTREEIFY_THRESHOLD)
				tab[index + bit] = hiHead.untreeify(map);
			else {
				tab[index + bit] = hiHead;
				if (loHead != null)
					hiHead.treeify(tab);
			}
		}
	}

	/* ------------------------------------------------------------ */
	// Red-black tree methods, all adapted from CLR

	static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
										  TreeNode<K,V> p) {
		TreeNode<K,V> r, pp, rl;
		if (p != null && (r = p.right) != null) {
			if ((rl = p.right = r.left) != null)
				rl.parent = p;
			if ((pp = r.parent = p.parent) == null)
				(root = r).red = false;
			else if (pp.left == p)
				pp.left = r;
			else
				pp.right = r;
			r.left = p;
			p.parent = r;
		}
		return root;
	}

	static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
										   TreeNode<K,V> p) {
		TreeNode<K,V> l, pp, lr;
		if (p != null && (l = p.left) != null) {
			if ((lr = p.left = l.right) != null)
				lr.parent = p;
			if ((pp = l.parent = p.parent) == null)
				(root = l).red = false;
			else if (pp.right == p)
				pp.right = l;
			else
				pp.left = l;
			l.right = p;
			p.parent = l;
		}
		return root;
	}

	static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
												TreeNode<K,V> x) {
		x.red = true;
		for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
			if ((xp = x.parent) == null) {
				x.red = false;
				return x;
			}
			else if (!xp.red || (xpp = xp.parent) == null)
				return root;
			if (xp == (xppl = xpp.left)) {
				if ((xppr = xpp.right) != null && xppr.red) {
					xppr.red = false;
					xp.red = false;
					xpp.red = true;
					x = xpp;
				}
				else {
					if (x == xp.right) {
						root = rotateLeft(root, x = xp);
						xpp = (xp = x.parent) == null ? null : xp.parent;
					}
					if (xp != null) {
						xp.red = false;
						if (xpp != null) {
							xpp.red = true;
							root = rotateRight(root, xpp);
						}
					}
				}
			}
			else {
				if (xppl != null && xppl.red) {
					xppl.red = false;
					xp.red = false;
					xpp.red = true;
					x = xpp;
				}
				else {
					if (x == xp.left) {
						root = rotateRight(root, x = xp);
						xpp = (xp = x.parent) == null ? null : xp.parent;
					}
					if (xp != null) {
						xp.red = false;
						if (xpp != null) {
							xpp.red = true;
							root = rotateLeft(root, xpp);
						}
					}
				}
			}
		}
	}

	static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
											   TreeNode<K,V> x) {
		for (TreeNode<K,V> xp, xpl, xpr;;)  {
			if (x == null || x == root)
				return root;
			else if ((xp = x.parent) == null) {
				x.red = false;
				return x;
			}
			else if (x.red) {
				x.red = false;
				return root;
			}
			else if ((xpl = xp.left) == x) {
				if ((xpr = xp.right) != null && xpr.red) {
					xpr.red = false;
					xp.red = true;
					root = rotateLeft(root, xp);
					xpr = (xp = x.parent) == null ? null : xp.right;
				}
				if (xpr == null)
					x = xp;
				else {
					TreeNode<K,V> sl = xpr.left, sr = xpr.right;
					if ((sr == null || !sr.red) &&
						(sl == null || !sl.red)) {
						xpr.red = true;
						x = xp;
					}
					else {
						if (sr == null || !sr.red) {
							if (sl != null)
								sl.red = false;
							xpr.red = true;
							root = rotateRight(root, xpr);
							xpr = (xp = x.parent) == null ?
								null : xp.right;
						}
						if (xpr != null) {
							xpr.red = (xp == null) ? false : xp.red;
							if ((sr = xpr.right) != null)
								sr.red = false;
						}
						if (xp != null) {
							xp.red = false;
							root = rotateLeft(root, xp);
						}
						x = root;
					}
				}
			}
			else { // symmetric
				if (xpl != null && xpl.red) {
					xpl.red = false;
					xp.red = true;
					root = rotateRight(root, xp);
					xpl = (xp = x.parent) == null ? null : xp.left;
				}
				if (xpl == null)
					x = xp;
				else {
					TreeNode<K,V> sl = xpl.left, sr = xpl.right;
					if ((sl == null || !sl.red) &&
						(sr == null || !sr.red)) {
						xpl.red = true;
						x = xp;
					}
					else {
						if (sl == null || !sl.red) {
							if (sr != null)
								sr.red = false;
							xpl.red = true;
							root = rotateLeft(root, xpl);
							xpl = (xp = x.parent) == null ?
								null : xp.left;
						}
						if (xpl != null) {
							xpl.red = (xp == null) ? false : xp.red;
							if ((sl = xpl.left) != null)
								sl.red = false;
						}
						if (xp != null) {
							xp.red = false;
							root = rotateRight(root, xp);
						}
						x = root;
					}
				}
			}
		}
	}

	/**
	 * Recursive invariant check
	 */
	static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
		TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
			tb = t.prev, tn = (TreeNode<K,V>)t.next;
		if (tb != null && tb.next != t)
			return false;
		if (tn != null && tn.prev != t)
			return false;
		if (tp != null && t != tp.left && t != tp.right)
			return false;
		if (tl != null && (tl.parent != t || tl.hash > t.hash))
			return false;
		if (tr != null && (tr.parent != t || tr.hash < t.hash))
			return false;
		if (t.red && tl != null && tl.red && tr != null && tr.red)
			return false;
		if (tl != null && !checkInvariants(tl))
			return false;
		if (tr != null && !checkInvariants(tr))
			return false;
		return true;
	}
}

可以看到TreeNode 其實就是紅黑樹的一個實現:

需要注意的點:

     在HashMap內部定義的幾個變數,包括桶陣列本身都是transient修飾的,這代表了他們無法被序列化,而HashMap本身是實現了Serializable介面的。這很容易產生疑惑:

HashMap是如何序列化的呢?

      查了一下原始碼發現,HashMap內有兩個用於序列化的函式 readObject(ObjectInputStream s) 和 writeObject(ObjectOutputStreams),通過這個函式將table序列化。

HashMap 的 put 方法解析

  以上就是我們對HashMap的初步認識,下面進入正題,看看HashMap是如何新增、查詢與刪除資料的。

  首先來看put方法,我儘量在每行都加註釋闡明這一行的含義,讓閱讀起來更容易理解。

public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true); 
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,          //這裡onlyIfAbsent表示只有在該key對應原來的value為null的時候才插入,也就是說如果value之前存在了,就不會被新put的元素覆蓋。
			   boolean evict) {                                              //evict引數用於LinkedHashMap中的尾部操作,這裡沒有實際意義。
	Node<K,V>[] tab; Node<K,V> p; int n, i;                    //定義變數tab是將要操作的Node陣列引用,p表示tab上的某Node節點,n為tab的長度,i為tab的下標。
	if ((tab = table) == null || (n = tab.length) == 0)                    //判斷當table為null或者tab的長度為0時,即table尚未初始化,此時通過resize()方法得到初始化的table。                        
		n = (tab = resize()).length;                        //這種情況是可能發生的,HashMap的註釋中提到:The table, initialized on first use, and resized as necessary。
	if ((p = tab[i = (n - 1) & hash]) == null)                               //此處通過(n - 1) & hash 計算出的值作為tab的下標i,並另p表示tab[i],也就是該連結串列第一個節點的位置。並判斷p是否為null。
		tab[i] = newNode(hash, key, value, null);                 //當p為null時,表明tab[i]上沒有任何元素,那麼接下來就new第一個Node節點,呼叫newNode方法返回新節點賦值給tab[i]。
	else {                                              //下面進入p不為null的情況,有三種情況:p為連結串列節點;p為紅黑樹節點;p是連結串列節點但長度為臨界長度TREEIFY_THRESHOLD,再插入任何元素就要變成紅黑樹了。
		Node<K,V> e; K k;                               //定義e引用即將插入的Node節點,並且下文可以看出 k = p.key。
		if (p.hash == hash &&                             //HashMap中判斷key相同的條件是key的hash相同,並且符合equals方法。這裡判斷了p.key是否和插入的key相等,如果相等,則將p的引用賦給e。
			((k = p.key) == key || (key != null && key.equals(k))))           //這一步的判斷其實是屬於一種特殊情況,即HashMap中已經存在了key,於是插入操作就不需要了,只要把原來的value覆蓋就可以了。
			e = p;                                    //這裡為什麼要把p賦值給e,而不是直接覆蓋原值呢?答案很簡單,現在我們只判斷了第一個節點,後面還可能出現key相同,所以需要在最後一併處理。
		else if (p instanceof TreeNode)                                       //現在開始了第一種情況,p是紅黑樹節點,那麼肯定插入後仍然是紅黑樹節點,所以我們直接強制轉型p後呼叫TreeNode.putTreeVal方法,返回的引用賦給e。
			e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);   //你可能好奇,這裡怎麼不遍歷tree看看有沒有key相同的節點呢?其實,putTreeVal內部進行了遍歷,存在相同hash時返回被覆蓋的TreeNode,否則返回null。
		else {                                                  //接下里就是p為連結串列節點的情形,也就是上述說的另外兩類情況:插入後還是連結串列/插入後轉紅黑樹。另外,上行轉型程式碼也說明了TreeNode是Node的一個子類。
			for (int binCount = 0; ; ++binCount) {                 //我們需要一個計數器來計算當前連結串列的元素個數,並遍歷連結串列,binCount就是這個計數器。
				if ((e = p.next) == null) {                     //遍歷過程中當發現p.next為null時,說明連結串列到頭了,直接在p的後面插入新的連結串列節點,即把新節點的引用賦給p.next,插入操作就完成了。注意此時e賦給p。
					p.next = newNode(hash, key, value, null);          //最後一個引數為新節點的next,這裡傳入null,保證了新節點繼續為該連結串列的末端。
					if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st     //插入成功後,要判斷是否需要轉換為紅黑樹,因為插入後連結串列長度加1,而binCount並不包含新節點,所以判斷時要將臨界閾值減1。
						treeifyBin(tab, hash);                     //當新長度滿足轉換條件時,呼叫treeifyBin方法,將該連結串列轉換為紅黑樹。
					break;                                //當然如果不滿足轉換條件,那麼插入資料後結構也無需變動,所有插入操作也到此結束了,break退出即可。
				}
				if (e.hash == hash &&                         //在遍歷連結串列的過程中,我之前提到了,有可能遍歷到與插入的key相同的節點,此時只要將這個節點引用賦值給e,最後通過e去把新的value覆蓋掉就可以了。
					((k = e.key) == key || (key != null && key.equals(k))))   //老樣子判斷當前遍歷的節點的key是否相同。
					break;                                //找到了相同key的節點,那麼插入操作也不需要了,直接break退出迴圈進行最後的value覆蓋操作。
				p = e;                                  //在第21行我提到過,e是當前遍歷的節點p的下一個節點,p = e 就是依次遍歷連結串列的核心語句。每次迴圈時p都是下一個node節點。
			}
		}
		if (e != null) { // existing mapping for key                //左邊註釋為jdk自帶註釋,說的很明白了,針對已經存在key的情況做處理。
			V oldValue = e.value;                           //定義oldValue,即原存在的節點e的value值。
			if (!onlyIfAbsent || oldValue == null)                 //前面提到,onlyIfAbsent表示存在key相同時不做覆蓋處理,這裡作為判斷條件,可以看出當onlyIfAbsent為false或者oldValue為null時,進行覆蓋操作。
				e.value = value;                              //覆蓋操作,將原節點e上的value設定為插入的新value。
			afterNodeAccess(e);                            //這個函式在hashmap中沒有任何操作,是個空函式,他存在主要是為了linkedHashMap的一些後續處理工作。
			return oldValue;                              //這裡很有意思,他返回的是被覆蓋的oldValue。我們在使用put方法時很少用他的返回值,甚至忘了它的存在,這裡我們知道,他返回的是被覆蓋的oldValue。
		}
	}                                            
	++modCount;                                      //收尾工作,值得一提的是,對key相同而覆蓋oldValue的情況,在前面已經return,不會執行這裡,所以那一類情況不算資料結構變化,並不改變modCount值。
	if (++size > threshold)                               //同理,覆蓋oldValue時顯然沒有新元素新增,除此之外都新增了一個元素,這裡++size並與threshold判斷是否達到了擴容標準。
		resize();                                     //當HashMap中存在的node節點大於threshold時,hashmap進行擴容。
	afterNodeInsertion(evict);                             //這裡與前面的afterNodeAccess同理,是用於linkedHashMap的尾部操作,HashMap中並無實際意義。1
	return null;                                        //最終,對於真正進行插入元素的情況,put函式一律返回null。
}

   在上述程式碼中的第十行,HashMap根據 (n - 1) & hash 求出了元素在node陣列的下標。這個操作非常精妙,下面我們仔細分析一下計算下標的過程,主要分三個階段:計算hashcode、高位運算和取模運算。

  首先,傳進來的hash值是由put方法中的hash(key)產生的(上述第2行),我們來看一下hash()方法的原始碼:

static final int hash(Object key) {
	int h;
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

  這裡通過key.hashCode()計算出key的雜湊值,然後將雜湊值h右移16位,再與原來的h做異或^運算——這一步是高位運算。設想一下,如果沒有高位運算,那麼hash值將是一個int型的32位數。而從2的-31次冪到2的31次冪之間,有將近幾十億的空間,如果我們的HashMap的table有這麼長,記憶體早就爆了。所以這個雜湊值不能直接用來最終的取模運算,而需要先加入高位運算,將高16位和低16位的資訊"融合"到一起,也稱為"擾動函式"。這樣才能保證hash值所有位的數值特徵都儲存下來而沒有遺漏,從而使對映結果儘可能的鬆散。最後,根據 n-1 做與操作的取模運算。這裡也能看出為什麼HashMap要限制table的長度為2的n次冪,因為這樣,n-1可以保證二進位制展示形式是(以16為例)0000 0000 0000 0000 0000 0000 0000 1111。在做"與"操作時,就等同於擷取hash二進位制值得後四位資料作為下標。這裡也可以看出"擾動函式"的重要性了,如果高位不參與運算,那麼高16位的hash特徵幾乎永遠得不到展現,發生hash碰撞的機率就會增大,從而影響效能。

  HashMap的put方法的原始碼實現就是這樣了,整理思路非常連貫。這裡面有幾個函式的原始碼(比如resize、putTreeValue、newNode、treeifyBin)限於篇幅原因,就不貼了,後面應該還會更新在其他部落格裡,有興趣的同學也可以自己挖掘一下。

HashMap 的 get 方法解析

   讀完了put的原始碼,其實已經可以很清晰的理清HashMap的工作原理了。接下來再看get方法的原始碼,就非常的簡單:

public V get(Object key) {
	Node<K,V> e;
	return (e = getNode(hash(key), key)) == null ? null : e.value;      //根據key及其hash值查詢node節點,如果存在,則返回該節點的value值。
}

final Node<K,V> getNode(int hash, Object key) {                  //根據key搜尋節點的方法。記住判斷key相等的條件:hash值相同 並且 符合equals方法。
	Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
	if ((tab = table) != null && (n = tab.length) > 0 &&            //根據輸入的hash值,可以直接計算出對應的下標(n - 1)& hash,縮小查詢範圍,如果存在結果,則必定在table的這個位置上。
		(first = tab[(n - 1) & hash]) != null) {
		if (first.hash == hash && // always check first node
			((k = first.key) == key || (key != null && key.equals(k))))    //判斷第一個存在的節點的key是否和查詢的key相等。如果相等,直接返回該節點。
			return first;
		if ((e = first.next) != null) {                       //遍歷該連結串列/紅黑樹直到next為null。
			if (first instanceof TreeNode)                       //當這個table節點上儲存的是紅黑樹結構時,在根節點first上呼叫getTreeNode方法,在內部遍歷紅黑樹節點,檢視是否有匹配的TreeNode。
				return ((TreeNode<K,V>)first).getTreeNode(hash, key);
			do {
				if (e.hash == hash &&                        //當這個table節點上儲存的是連結串列結構時,用跟第11行同樣的方式去判斷key是否相同。
					((k = e.key) == key || (key != null && key.equals(k))))
					return e;
			} while ((e = e.next) != null);                      //如果key不同,一直遍歷下去直到連結串列盡頭,e.next == null。
		}
	}
	return null;
}

        因為查詢過程不涉及到HashMap的結構變動,所以get方法的原始碼顯得很簡潔。核心邏輯就是遍歷table某特定位置上的所有節點,分別與key進行比較看是否相等。

----------------------------------------------------------------------------------

  以上便是HashMap最常用API的原始碼分析,除此之外,HashMap還有一些知識需要重點學習:擴容機制、併發安全問題、內部紅黑樹的實現。這些內容我也會在之後陸續發文分析,希望可以幫讀者徹底理解HashMap的原理。