HashMap源码逐行分析

2 篇文章 0 订阅
1 篇文章 0 订阅

目录

1.数据结构

1.Node

2.LinkedHashMap$Entry

3.TreeNode

1.类结构

2.root()

3.moveRootToFront()

4.find()

5.tieBreakOrder()

6.treeify()

7.untreeify()

8.balanceInsertion()

9.banalceDeletion()

10.split()

11.putTreeVal()

12.removeTreeNode()

2.HashMap

1.静态和成员变量

2.构造方法

3.静态工具方法

1.hash()

2.comparableClassFor()

3.compareComparables()

4.tableSizeFor()

4.putVal()

5.resize()

6.treeifyBin()

7.getNode()

8.removeNode()


1.数据结构

1.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;
	}
}

2.LinkedHashMap$Entry

多了before、after指针,用来维护顺序(insert order/access order)

static class Entry<K,V> extends HashMap.Node<K,V> {
	Entry<K,V> before, after;
	Entry(int hash, K key, V value, Node<K,V> next) {
		super(hash, key, value, next);
	}
}

3.TreeNode

1.类结构

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
	TreeNode<K,V> parent;  // 父节点
	TreeNode<K,V> left; // 左子树指针
	TreeNode<K,V> right; // 右子树指针
	TreeNode<K,V> prev;    // prev和next指针维护双向链表 
	boolean red; // 节点颜色
	TreeNode(int hash, K key, V val, Node<K,V> next) {
		super(hash, key, val, next);
	}
	
	...
	
}

2.root()

查找红黑树的根节点

final TreeNode<K,V> root() {
	for (TreeNode<K,V> r = this, p;;) {
		if ((p = r.parent) == null)
			return r;
		r = p;
	}
}

3.moveRootToFront()

保持红黑树的root节点永远处于slot上的第一个元素

因为这个方法是在红黑树平衡后调用 所以是不需要调整红黑树结构的 但是TreeNode维护的双向链表可能是需要调整的

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];
    	// 如果当前slot的第一个元素不是root节点
		if (root != first) {
			Node<K,V> rn;
			tab[index] = root; // slot第一个元素改为root节点
            
            // 将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);
	}
}

4.find()

从红黑树的当前节点,一直向下搜索保存了给定Key的节点

确定红黑树的搜索方向,首先比较哈希值

  • 如果哈希值不一样,向左/右子树继续搜索

  • 如果哈希值一样,立马进行Key的比较,如果Key也一样,则立即返回当前节点

  • 如果哈希值一样,但是Key不一样,则进行Key的Class类型比较,来确定继续搜索的方向

    • 如果依然确定不了搜索方向,则先从右子树向下搜索,如果搜索不到再去左子树向下搜索

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;
        
        // hash一样 key地址值(equals判断)一样 直接返回当前节点
		else if ((pk = p.key) == k || (k != null && k.equals(pk))) 
			return p;
        
        // hash一样时 如果不存在左子树 则下次从右子树开始寻找
		else if (pl == null)
			p = pr;
        // hash一样时 如果不存在右子树 则下次从左子树开始寻找
		else if (pr == null)
			p = pl; 
        
        // hash一样时 左右子树都存在 则进行Key的Class类型比较来确定搜索方向
		else if ((kc != null ||
				  (kc = comparableClassFor(k)) != null) &&
				 (dir = compareComparables(kc, k, pk)) != 0)
			p = (dir < 0) ? pl : pr;
        
        // 哈希一样时 左右子树都存在 且上面Key的Class类型比较依然确定不了方向
        // 则从右子树向下搜索 如果搜索到了立马返回
		else if ((q = pr.find(h, k, kc)) != null)
			return q;
        
        // 走到这里 说明上面从右子树去搜索没搜索到  
        // 这个时候再去左子树上搜索
		else
			p = pl;
	} while (p != null);
	return null;
}

5.tieBreakOrder()

当哈希值相同,且Key的Class类型比较依然相同时,通过这个方法来最终确定方向

  • 如果a和b都不为null,则进行全类名字符串的比较,比较结果不为0立即返回

  • 如果a或者b为null,或者上面的全类名字符串比较结果依然是0,则通过System.identityHashCode()拿到哈希值进行比较

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;
}

6.treeify()

将TreeNode维护的双向链表上的节点挨个取出来,然后生成一棵红黑树

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;
            
            // 如果根节点不为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;
                // dir<=0时 且左子树为null 则当前节点就是待插入节点的父节点
                // dir>0时 且右子树为null 则当前节点就是待插入节点的父节点
				if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    // 维护二叉搜索树的关系
					x.parent = xp;
					if (dir <= 0)
						xp.left = x;
					else
						xp.right = x;
                    
                    // 节点插入后的平衡操作 然后跳出循环 继续链表上next节点的插入
					root = balanceInsertion(root, x);
					break;
				}
			}
		}
	}
    
    // 红黑树生成后 将root节点移动到slot第一个元素
	moveRootToFront(tab, root);
}

7.untreeify()

将双向链表上维护的TreeNode变为普通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;
}

8.balanceInsertion()

二叉搜索树插入节点后的平衡操作,平衡后返回红黑树根节点

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
											TreeNode<K,V> x) { // x为新插入的节点
    
	x.red = true; // 红黑树规则:新插入节点都是红色
	for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
		if ((xp = x.parent) == null) { // 父节点为null 说明当前节点是根节点 染黑后结束
			x.red = false;
			return x;
		}
        
        // 父节点不为null时 如果父节点是黑色或者父节点是根节点 不需要平衡
		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) { // LR      
                    // 这里相当于两步操作 rotate(root, xp); x = 旋转后的xp;
                    // 这个时候的x其实就是左旋到下面去的之前的父节点 而xp则是之前父节点的右子树
					root = rotateLeft(root, x = xp); 
					xpp = (xp = x.parent) == null ? null : xp.parent; // 最新的祖父节点
				}
				if (xp != null) { // LL
                    // 将xp染黑 将xpp染红 然后对xpp进行右旋
					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) { // RL
					root = rotateRight(root, x = xp); // 先对父节点进行右旋
					xpp = (xp = x.parent) == null ? null : xp.parent; // 更新祖父节点
				}
				if (xp != null) { // RR
					xp.red = false; 
					if (xpp != null) {
						xpp.red = true;
						root = rotateLeft(root, xpp); // 对祖父节点进行左旋 相关节点染色
					}
				}
			}
		}
	}
}
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) {
        // p.right = r.left;
        // rl = r.left;
        // if(rl != null) rl.parent = p;
		if ((rl = p.right = r.left) != null)
			rl.parent = p;
        
        // r.parent = p.parent;
        // pp = p.parent;
        // if (pp == null) { root = r; root.red = false; } 
		if ((pp = r.parent = p.parent) == null)
			(root = r).red = false;
        
        // if (pp != null && p == pp.left) pp.left = r;
		else if (pp.left == p)
			pp.left = r;
        // if (pp != null && p = pp.right) pp.right = r;
		else
			pp.right = r;
        
        // p.parent = r; r.left = p;
		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;
}

9.banalceDeletion()

红黑树节点删除后的平衡操作,删除后的平衡操作逻辑要比添加复杂

这里的x节点是真正删除节点的替代节点,能来到这里,说明基本二叉搜索树删除节点后的指针关系已经维护完了

static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
										   TreeNode<K,V> x) {
	for (TreeNode<K,V> xp, xpl, xpr;;)  {
        // 如果替代节点为null或者是root 直接return
		if (x == null || x == root)
			return root;
        
        // 如果替代节点是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) { // 替代节点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 {  
                    // 如果兄弟节点的左节点是红色的 先对兄弟节点进行一次右旋 构造RR情况
                    // 旋转后更新兄弟节点 
					if (sr == null || !sr.red) { // !sr.red => sl.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 { // 删除节点的兄弟节点是左子树 与上面的情况是镜像对映的
			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) { // !sl.red => sr.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;
				}
			}
		}
	}
}

10.split()

这个方法只会在resize()方法中调用,将红黑树上TreeNode维护的双向链表拆为高低链表

  • 若拆完后高/低链表上的节点个数小于等于6,则把高/低链表上面的TreeNode变为普通Node,然后将普通Node节点维护的单链表放到新哈希表的目标slot上

  • 若拆完后高/低链表上的节点个数大于6,则将高/低链表上的TreeNode维护的双向链表转为红黑树

注意啦!!put()和resize()过程中的红黑树数据结构转变,都是先维护链表,然后再将链表转为红黑树

/**
 * map为旧哈希表 tab为新哈希表 index为旧表上的slot对应索引 bit为oldCap
 */
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { 
	TreeNode<K,V> b = this;
	
    // 这里的逻辑与下面resize()方法中 对于普通单链表处理是一样的 拆分高低位链表
    // 低位即节点的Key的哈希值与旧表容量与运算后结果为0 高位即与运算后结果不为0 
	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) {
        // 低位链表上的节点个数小于等于6 则将低位链表上的TreeNode转为普通Node
		if (lc <= UNTREEIFY_THRESHOLD)
			tab[index] = loHead.untreeify(map);
        
        // 低位链表上的节点个数大于6 则将低位链表上的TreeNode挨个取出来组成新的红黑树插入新哈希表的目标slot上
		else {
            // 将低位链表头节点放到新哈希表上
			tab[index] = loHead;
            // 这个判断很精髓!!!
            // 如果hiHead为null 代表高位链表上根本没节点 则通过上面一行代码已经完成了新红黑树生成
            // 因为这个时候 新红黑树和旧红黑树是完全一样的
			if (hiHead != null) 
				loHead.treeify(tab);
		}
	}
    
    // 与上面对低位链表分析是对应的 新哈希表的目前slot索引是旧索引加旧表容量
	if (hiHead != null) {
		if (hc <= UNTREEIFY_THRESHOLD)
			tab[index + bit] = hiHead.untreeify(map);
		else {
			tab[index + bit] = hiHead;
			if (loHead != null)
				hiHead.treeify(tab);
		}
	}
}

11.putTreeVal()

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;;) {
        
        // 下面一段都是确认搜索方向的逻辑 与上面分析过的find()类似
		int dir, ph; K pk;
		if ((ph = p.hash) > h)
			dir = -1;
		else if (ph < h)
			dir = 1;
        
        // 如果哈希值一样 且key一样 则返回这个节点 在putVal()后续进行value更新
		else if ((pk = p.key) == k || (k != null && k.equals(pk)))
			return p;
        
        // 如果哈希值一样 Key不一样 且Key的Class类型比较后结果依然为0
        // 会在searched为false 会去搜索当前节点的左子树和右子树 看是不是由某个节点的Key与指定Key是一样的 
        // 如果第一次没找到 那说明以当前节点为根节点的树上根本就没这样一个包含指定Key的节点 之后就没必要在搜索了 
        // 为什么在这里要搜索呢?因为当前节点的Key与指定Key不同 但左右子树上可能存在一样的节点 
		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;
			}
            
            // 如果没搜索到 因为上面Key的Class类型比较结果为0 
            // 所以要通过这个方法来强制确定待插入节点的父节点的搜索方向
			dir = tieBreakOrder(k, pk);
		}

		TreeNode<K,V> xp = p;
        // 待插入节点的夫节点已经找到了 就是xp
		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); // 注意!这行代码里面有 x.next = 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;
            
            // 先进行红黑树新节点插入后的平衡操作 如果平衡后root变了 则将新root移动到slot第一个元素
			moveRootToFront(tab, balanceInsertion(root, x));
			return null;
		}
	}
}

12.removeTreeNode()

这个方法复杂的原因有两点:

  • 相比较简单二叉搜索树,找到前驱后继节点后的值替换,然后删除前驱或者后继节点即可,但是这里交换的不是值而是指针

  • 红黑树的节点删除后的平衡操作本来就很复杂

下面这个方法的逻辑:

  • 维护双向链表,如果双向链表上元素太少,转为普通单链表

  • 找到后继节点,先交换删除节点和后继节点颜色,然后交换它们的指针,然后确定后继节点的替代节点

  • 删除后继节点,维护二叉搜索树关系,然后进行节点删除后的平衡操作

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;
    
    // 拿到红黑树的根节点,拿到双向链表的prev和succ指针
	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)	// 如果prev为null 说明是双向链表头节点 这个时候将头节点变为succ
		tab[index] = first = succ;
	else
		pred.next = succ;
	if (succ != null)
		succ.prev = pred;
	if (first == null)
		return;
    
    // 判断此时红黑树上面的元素个数 如果根节点、左子树、右子树、或者左子树的左子树为null  
    // 则将红黑树变为单链表
	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;
	}
    
    // 下面这部分是红黑树节点的删除 this为要删除的节点本身
	TreeNode<K,V> p = this, pl = left, pr = right, replacement;
    
    // 如果这个节点同时存在左右子树
	if (pl != null && pr != null) {
		TreeNode<K,V> s = pr, sl;
        // s为后继节点
		while ((sl = s.left) != null) 
			s = sl;
        
        // 找到后继节点后 先交换删除节点和其后继节点颜色
		boolean c = s.red; s.red = p.red; p.red = c; 
        
        // 下面这部分逻辑是 交换后继节点s和删除节点的指针
		TreeNode<K,V> sr = s.right;
		TreeNode<K,V> pp = p.parent;
        
		if (s == pr) {  // 如果后继节点直接是删除节点右子树
			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;
        
        // 因为是后继节点 所以sl肯定是null 如果存在sr不为null 则替代节点就是sr
        // 否则替代节点就是p
		if (sr != null)
			replacement = sr;
		else
			replacement = p;
	}
	else if (pl != null)
		replacement = pl;
	else if (pr != null)
		replacement = pr;
	else
		replacement = p;
    
    // 对照下面 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;
	}
	
    // 参见上面balanceDeletion()分析
	TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
	
    
    // 如果替代节点就是p 说明后继节点之前就是p的右子树 经过指针交换后后继节点的替代节点才是p
    // 这个时候因为之前已经维护完指针了 所以只需要断掉后继节点的引用即可
	if (replacement == p) {  
		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);
}

2.HashMap

1.静态和成员变量

// 默认容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

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

// 默认负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 当slot上链表元素个数大于8(包含头节点)时 将链表转为红黑树
static final int TREEIFY_THRESHOLD = 8;

// 当红黑树上面的节点个数小于6时 将红黑树转为链表
static final int UNTREEIFY_THRESHOLD = 6;

// 当数组容量到64时 才会进行数据结构转变 否则扩容
static final int MIN_TREEIFY_CAPACITY = 64;
// 底层的数组
transient Node<K,V>[] table;

// 键值对集合
transient Set<Map.Entry<K,V>> entrySet;

// 键值对个数
transient int size;

// 记录对Map的修改次数 遍历时支持fail-fast
transient int modCount;

// 哈希表扩容临界值
int threshold;

// 负载因子
final float loadFactor;

2.构造方法

// 默认构造方法只是设定了负载因子 其余都没初始化
public HashMap() {
	this.loadFactor = DEFAULT_LOAD_FACTOR; 
}
public HashMap(int initialCapacity) {
	this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap(int initialCapacity, float loadFactor) {
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal initial capacity: " +
										   initialCapacity);
	if (initialCapacity > MAXIMUM_CAPACITY)
		initialCapacity = MAXIMUM_CAPACITY;
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor: " +
										   loadFactor);
	this.loadFactor = loadFactor;
	this.threshold = tableSizeFor(initialCapacity); // 这里计算的临界值是通过tableSizeFor()得到的
}

3.静态工具方法

1.hash()

  • 如果key为null,返回0

  • 否则先调用key的hashCode()方法计算一个初始哈希值,然后将它无符号右移16位后,进行异或操作

这样做的操作是混合初始哈希值的高位特征和地位特征,因为数组长度一般比较小,所以进行与运算寻址时,主要是哈希值的低位起作用,这个时候如果高位不同、低位相同还是会哈希冲突

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

2.comparableClassFor()

通过反射泛型的方式来判断X的泛型是否实现了Comparable接口,如果没实现返回null

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;
}

3.compareComparables()

如果k和x都实现了Comparable接口,通过比较接口来进行比较

static int compareComparables(Class<?> kc, Object k, Object x) {
	return (x == null || x.getClass() != kc ? 0 :
			((Comparable)k).compareTo(x));
}

4.tableSizeFor()

计算一个大于等于cap的最小的2的N次方的数值

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;
}

4.putVal()

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
			   boolean evict) {
	Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    // 第一次调用这个方法 数组还未初始化 先通过扩容方法完成数组初始化
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;
    
    // 寻址到的slot上如果为null 这个时候不存在哈希冲突
    // 不用考虑什么数据结构 直接new一个Node放上去完事 
	if ((p = tab[i = (n - 1) & hash]) == null)
		tab[i] = newNode(hash, key, value, null);
    
	else { // 出现哈希冲突了
        
        // e代表与指定key相同的某个node 如果存在则下面进行value的更新
		Node<K,V> e; K k;
        
        // 如果哈希值一样 则e为当前节点 
		if (p.hash == hash &&
			((k = p.key) == key || (key != null && key.equals(k))))
			e = p;
        
        // 如果p是TreeNode 则在红黑树上插入节点
        // 根据上面对TreeNode分析 这里的p肯定是红黑树的root
		else if (p instanceof TreeNode)
			e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        
        // 链表
		else {
			for (int binCount = 0; ; ++binCount) {
                // 遍历链表后 没有包含指定key的节点 则new一个node进行尾插
				if ((e = p.next) == null) {
					p.next = newNode(hash, key, value, null);
                    
                    // 注意当binCount为0时 其实遍历到了链表的第二个节点
                    // 所以当binCount大于等于7时 链表上已经有了8个节点 而这时正在插入第9个节点
					if (binCount >= TREEIFY_THRESHOLD - 1) 
                        // 判断是否要将链表转为红黑树
						treeifyBin(tab, hash);
					break;
				}
                
                // 遍历链表找到了包含指定key的node 跳出循环
				if (e.hash == hash &&
					((k = e.key) == key || (key != null && key.equals(k))))
					break;
				p = e;
			}
		}
        
        // 找到了包含指定Key的节点 这个时候进行value更新 并返回旧value
		if (e != null) { 
			V oldValue = e.value;
			if (!onlyIfAbsent || oldValue == null)
				e.value = value;
			afterNodeAccess(e);
			return oldValue;
		}
	}
    
    // 如果是新插入节点 modCount加1 size加1
	++modCount;
    // 如果size超过了临界值 进行扩容
	if (++size > threshold)
		resize();
    
    // 这个方法是钩子方法 具体实现在LinkedHashMap中有
	afterNodeInsertion(evict);
	return null;
}

5.resize()

final Node<K,V>[] resize() {
    // 拿到旧table的容量和临界值
	Node<K,V>[] oldTab = table;
	int oldCap = (oldTab == null) ? 0 : oldTab.length;
	int oldThr = threshold;
	int newCap, newThr = 0;
    
    // 如果旧容量大于0 说明已经经过了初始化了
	if (oldCap > 0) {
		if (oldCap >= MAXIMUM_CAPACITY) {
			threshold = Integer.MAX_VALUE;
			return oldTab;
		}
        // 新容量为旧容量的2倍 新临界值也为旧临界值的2倍
		else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
				 oldCap >= DEFAULT_INITIAL_CAPACITY)
			newThr = oldThr << 1; 
	}
    
    // 当调用有参构造方法传入一个给定容量后 会将临界值变为一个大于等于给定容量的最小的2的N次方
    // 所以初始化时就取旧临界值即可
	else if (oldThr > 0) 
		newCap = oldThr;
    
    // 说明调用的是默认构造方法 旧容量和旧临界值都是0
    // 初始化容量是16 临界值是12
	else {               
		newCap = DEFAULT_INITIAL_CAPACITY;
		newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
	}
    
    // 计算新的临界值 新临界值是新容量乘负载因子
	if (newThr == 0) {
		float ft = (float)newCap * loadFactor;
		newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
				  (int)ft : Integer.MAX_VALUE);
	}
	threshold = newThr;
    
    // 进行底层Node数组初始化
	@SuppressWarnings({"rawtypes","unchecked"})
		Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	table = newTab;
    
    // 如果旧表不为null 则要把旧哈希表的节点取出来重新散列到新哈希表上去
	if (oldTab != null) {
		for (int j = 0; j < oldCap; ++j) {
			Node<K,V> e;
            
            // 拿到每个slot上的第一个节点 然后将旧slot置为null
			if ((e = oldTab[j]) != null) {
				oldTab[j] = null; 
                
                // 如果不存在next节点 则根据哈希值寻址后 放到新表的目标slot上
				if (e.next == null)
					newTab[e.hash & (newCap - 1)] = e;
                
                // 如果当前节点是红黑树的根节点 则进行split操作
				else if (e instanceof TreeNode)
					((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                // 如果当前节点是链表的头节点 则要把单条链表变为两条链表
                // 分别是高位链表和低位链表 
				else { 
					Node<K,V> loHead = null, loTail = null;
					Node<K,V> hiHead = null, hiTail = null;
					Node<K,V> next;
					do {
						next = e.next;
						if ((e.hash & oldCap) == 0) { // 如果节点的哈希值与旧容量进行与运算后是0 则放到低位链表
							if (loTail == null)
								loHead = e;
							else
								loTail.next = e;
							loTail = e;
						} 
						else { // 如果节点哈希值与旧容量进行与运算后不为0  则放到高位链表
							if (hiTail == null)
								hiHead = e;
							else
								hiTail.next = e;
							hiTail = e;
						}
					} while ((e = next) != null);
                    
                    // 将之前链表拆为高低链表后 再放入新哈希表上
                    // 低位链表在新哈希表存放的slot索引与旧哈希表一样
                    // 高位链表在新哈希表存放的slot索引是(旧slot索引加上旧哈希表容量)
					if (loTail != null) {
						loTail.next = null;
						newTab[j] = loHead;
					}
					if (hiTail != null) {
						hiTail.next = null;
						newTab[j + oldCap] = hiHead;
					}
				}
			}
		}
	}
	return newTab;
}

6.treeifyBin()

final void treeifyBin(Node<K,V>[] tab, int hash) {
	int n, index; Node<K,V> e;
    // 如果tab的长度没有到64 则进行扩容
	if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
		resize();
    
    // tab长度大于等于64了 这个时候就把slot上的单链表转为红黑树了 
    // 首先将单链表上的节点由普通Node变为TreeNode 然后维护起来一个双向链表 最后通过双向链表来生成红黑树
	else if ((e = tab[index = (n - 1) & hash]) != null) {
		TreeNode<K,V> hd = null, tl = null;
		do {
			TreeNode<K,V> p = replacementTreeNode(e, null);
			if (tl == null)
				hd = p;
			else {
				p.prev = tl;
				tl.next = p;
			}
			tl = p;
		} while ((e = e.next) != null);
		if ((tab[index] = hd) != null)
			hd.treeify(tab);
	}
}

7.getNode()

final Node<K,V> getNode(int hash, Object key) {
	Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
	if ((tab = table) != null && (n = tab.length) > 0 &&
		(first = tab[(n - 1) & hash]) != null) { // 如果slot上为null 那肯定找不到
        
        // slot上第一个节点Key与查询Key一样 则直接返回第一个节点
		if (first.hash == hash && 
			((k = first.key) == key || (key != null && key.equals(k))))
			return first;
        
        // 遍历后续节点 按照红黑树和单链表分别处理
		if ((e = first.next) != null) {
			if (first instanceof TreeNode)
				return ((TreeNode<K,V>)first).getTreeNode(hash, key);
			do {
				if (e.hash == hash &&
					((k = e.key) == key || (key != null && key.equals(k))))
					return e;
			} while ((e = e.next) != null);
		}
	}
	return null;
}
// 这个方法里面的核心方法就是find() 上面已经分析过了
final TreeNode<K,V> getTreeNode(int h, Object k) {
	return ((parent != null) ? root() : this).find(h, k, null);
}

8.removeNode()

final Node<K,V> removeNode(int hash, Object key, Object value,
						   boolean matchValue, boolean movable) {
	Node<K,V>[] tab; Node<K,V> p; int n, index;
	if ((tab = table) != null && (n = tab.length) > 0 &&
		(p = tab[index = (n - 1) & hash]) != null) {
        
        // 这部分是逻辑是先去找到包含指定Key的Node 第一个节点不匹配后 区分红黑树和单链表逻辑
		Node<K,V> node = null, e; K k; V v;
		if (p.hash == hash &&
			((k = p.key) == key || (key != null && key.equals(k))))
			node = p;
		else if ((e = p.next) != null) {
			if (p instanceof TreeNode)
				node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
			else {
				do {
					if (e.hash == hash &&
						((k = e.key) == key ||
						 (key != null && key.equals(k)))) {
						node = e;
						break;
					}
					p = e;
				} while ((e = e.next) != null);
			}
		}
        
        // node就是找到的包含指定Key的节点 remove()里面传递的matchValue为false
        // 如果matchVaulue为true 则需要再校验node节点的Value与指定Value是否一样
		if (node != null && (!matchValue || (v = node.value) == value ||
							 (value != null && value.equals(v)))) {
            
            // 红黑树节点删除
			if (node instanceof TreeNode)
				((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            
            // 单链表节点删除
			else if (node == p)
				tab[index] = node.next;
			else
				p.next = node.next;
			++modCount;
			--size;
			afterNodeRemoval(node); // 钩子方法
			return node;
		}
	}
	return null;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值