首页 > IT职业 > IT生活 > Java1.8的HashMap源码解析
java1.8是现在用的最多的版本,hashmap是现在用的最多的map,今天我们试图分析一下源码。
首先我们注意到数据是存放在一个Node数组里面
transient Node[] table;
接着我们看一下Node
static class Nodeimplements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node 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; } }
我们注意到这是一个单链表,next指向下一个节点。
接着我们看一下get(Object key)方法
public V get(Object key) {
Node e; return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final NodegetNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode )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; }
首先定位到数据在数组的下标: (n - 1) & hash
找到数组的第一个node:first,如果first为null直接返回
如果first的key和get里面的key相等,则返回first的value
如果first的key和get里面的key不相等,判断first是不是TreeNode,如果不是,则一直找next,直到key和所传的key相等
如果first的key和get里面的key不相等,判断first是不是TreeNode,如果是,则调用getTreeNode方法查找
我们看一下TreeNode的数据结构
static final class TreeNodeextends LinkedHashMap.Entry { TreeNode parent; // red-black tree links TreeNode left; TreeNode right; TreeNode prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node next) { super(hash, key, val, next); } ...... ...... }
再看下LinkedHashMap.Entry
static class Entryextends HashMap.Node { Entry before, after; Entry(int hash, K key, V value, Node next) { super(hash, key, value, next); } }
我们注意到LinkedHashMap.Entry把单链表扩展成了双向链表
TreeNode把双向链表扩张成了红黑树
我们在来看一下getTreeNode方法
final TreeNodegetTreeNode(int h, Object k) { return ((parent != null) ? root() : this).find(h, k, null); }
root()是查询根节点,接着看一下find方法
final TreeNodefind(int h, Object k, Class> kc) { TreeNode p = this; do { int ph, dir; K pk; TreeNode pl = p.left, pr = p.right, q; if ((ph = p.hash) > h) p = pl; else if (ph 如上,要查询的key的hash为h,依次遍历节点,如果节点的hash>h,则把节点的左节点赋值给节点,如果节点的hash
put方法
接着我们看一下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, 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; } 我们一行行分析
先是拿到数组的长度:n = (tab = resize()).length;
取到数据在数组的下标:i = (n - 1) & hash
取到下标i对应的节点p = tab[i]
如果p为null,则新建一个node: tab[i] = newNode(hash, key, value, null);NodenewNode(int hash, K key, V value, Node next) { return new Node(hash, key, value, next); } 如果p不为null,判断p的key和传的key是否相等,相等则返回p
如果p不为null,判断p的key和传的key是否相等,不相等,如果是TreeNode则放入红黑树里面
如果p不为null,判断p的key和传的key是否相等,不相等,如果不是TreeNode,则一直遍历next节点,知道节点的key和传的key相等
如果一直遍历到最后也没找到,则新建一个节点,并把它放在链表的末尾最后新建节点的时候有一个树化 的判断
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);TREEIFY_THRESHOLD的值为8,超过8个则由链表转换成红黑树
我们来看一个这个方法final void treeifyBin(Node[] tab, int hash) { int n, index; Node e; if (tab == null || (n = tab.length) hd = null, tl = null; do { TreeNode 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); } } 取到链表的首节点:e = tab[index = (n - 1) & hash]
replacementTreeNode方法实际是new一个TreeNodeTreeNodereplacementTreeNode(Node p, Node next) { return new TreeNode(p.hash, p.key, p.value, next); } do while操作是做了一个转换,转换成双向链表
最后一句hd.treeify(tab);是调用树化的方法,我们看一下这个方法final void treeify(Node[] tab) { TreeNode root = null; for (TreeNode x = this, next; x != null; x = next) { next = (TreeNode )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 p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph xp = p; if ((p = (dir 注意这个方法的this是下标为i的数组的首节点
如果root为空,初始化root,根节点是黑色if (root == null) { x.parent = null; x.red = false; root = x; }外面的for循环,遍历next节点,把所有的节点插入进树里面
里面的For循环,通过hash对比判断左节点还是右节点,从根节点找左右节点,并把左右节点当成根节点往下找,直到左节点或者右节点为空,把它安装在这里我们先看一下红黑树的特性,在看下面的balanceInsertion(root, x)方法
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。staticTreeNode balanceInsertion(TreeNode root, TreeNode x) { x.red = true; for (TreeNode 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); } } } } } } 通过判断决定是左旋转还是右旋转,如上标两个感叹号的地方,是右节点没有,树不平衡了,此时x等于xp.right,这时候就左旋,否则就右旋
左旋转staticTreeNode rotateLeft(TreeNode root, TreeNode p) { TreeNode 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; }
右旋转
staticTreeNode rotateRight(TreeNode root, TreeNode p) { TreeNode 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; }
最后,把根节点设置在第一位
/** * Ensures that the given root is the first node of its bin. */ staticvoid moveRootToFront(Node [] tab, TreeNode root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { int index = (n - 1) & root.hash; TreeNode first = (TreeNode )tab[index]; if (root != first) { Node rn; tab[index] = root; TreeNode rp = root.prev; if ((rn = root.next) != null) ((TreeNode )rn).prev = rp; if (rp != null) rp.next = rn; if (first != null) first.prev = root; root.next = first; root.prev = null; } assert checkInvariants(root); } } 如果已经是红黑树了,插入进节点的方法
/** * 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 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 xpn = xp.next; TreeNode x = map.newTreeNode(h, k, v, xpn); if (dir )xpn).prev = x; moveRootToFront(tab, balanceInsertion(root, x)); return null; } } } 和上面基本差不多,也是平衡,旋转,把root置为first。
Java1.8的HashMap源码解析
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/930/viewspace-2810733/ ,如需转载,请注明出处,否则将追究法律责任。