举例一个入口,利用一个Map构造HashMap时
/** * Constructs a new HashMap with the same mappings as the * specified Map. The HashMap is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified Map. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public HashMap(Map m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
然后就是调用putMapEntries
方法,第二个参数其实可以看作细节,个人认为它和HashMap的子类LinkedHashMap有关,evict
是逐出的意思,如果基于LinkedHashMap实现LRU缓存的话,这个evict参数正好就用上了。
/** * Implements Map.putAll and Map constructor * * @param m the map * @param evict false when initially constructing this map, else * true (relayed to method afterNodeInsertion). */ final void putMapEntries(Map m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
可以看到在for循环中遍历旧的entrySet视图,然后将一个个的key-value对放入新构造的HashMap中,
for (Map.Entry e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); }
展开putVal(hash(key), key, value, false, evict);
/** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); else { 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; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
通过hash(key)定位到HashMap中tab数组的索引,如果这个数组元素的头节点正好是TreeNode类型,那么就将执行
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
此时this
是HashMap自身。
/** * Tree version of putVal. */ final TreeNodeputTreeVal(HashMap map, Node [] tab, int h, K k, V v) { Class kc = null; boolean searched = false; TreeNode root = (parent != null) ? root() : this; for (TreeNode 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 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 xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { Node xpn = xp.next; TreeNode 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 )xpn).prev = x; moveRootToFront(tab, balanceInsertion(root, x)); return null; } } }
下面重点分析putTreeVal方法
1 首先找到root节点,TreeNoderoot = (parent != null) ? root() : this;
这里的this是指TreeNode自己,从某个节点一直往上溯,直到parent==null的情况
2 递归遍历root判断节点之间的hash大小,如果hash值相等采用key比较等然后采用左子树或者右子树,继续遍历(关于key值大小的比较算是细节的地方,这里暂且代入String类型的key解读源码以图整体思路流畅)3 如果遍历到了叶子节点比如上一步采用左子树,而左子树刚好是叶子节点,p == null此时递归遍历结束if ((p = (dir <= 0) ? p.left : p.right) == null) { Nodexpn = xp.next; TreeNode 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 )xpn).prev = x; moveRootToFront(tab, balanceInsertion(root, x)); return null; }
xp是叶子节点的父节点,这个节点不是null,叶子节点p一定是null
新增一个节点x,next指向原来父节点的.next,x就是新增的叶子节点1) 处理红黑树的关系父节点xp和叶子节点x的关系,落在左子树还是右子树;x的parent指向父节点xpx.parent = xp
最后保持红黑树平衡2)处理双向链表的关系类似于在xp-->xpn(xp.next)中间插入新的节点x,即 x = map.newTreeNode(h, k, v, xpn);x.prev = xp//如果xpn不是null,则处理xpn的prev((TreeNode)xpn).prev = x;
图示
3) 保持红黑树平衡
moveRootToFront(tab, balanceInsertion(root, x));