这章开始我们对Map的具体实现类来进行介绍,首先是HashMap,而Map中HashMap是最常用的。
基本属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ; static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int TREEIFY_THRESHOLD = 8 ; static final int UNTREEIFY_THRESHOLD = 6 ; static final int MIN_TREEIFY_CAPACITY = 64 ; 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 ; } }
字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 transient Node<K,V>[] table; transient Set<Map.Entry<K,V>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor;
静态工具方法 hash 1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
key
的hash
值高16不变,低16位与高16位异或作为key
的最终hash
值 。(h >>> 16)代表无符号右移16位,高位补0,任何数跟0异或都是其本身,因此key
的hash
值高16位不变。
整个过程本质上就是三步:
拿到key
的hashCode
值
将hashCode
值高位参与运算,重新计算hash
值
将计算出的hash
值与(table.length - 1)
进行&
运算
compareComparables 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static Class<?> comparableClassFor(Object x) { if (x instanceof Comparable) { Class<?> c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) 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) return c; } } } return null ; }
如果x实现了Comparable接口就返回x的Class对象,否则返回null。
compareComparables 1 2 3 4 static int compareComparables (Class<?> kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); }
如果x的Class对象等于kc,就调用compareTo返回比较结果,否则返回0。
tableSizeFor 1 2 3 4 5 6 7 8 9 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 ; }
用于找到大于等于cap的最小2的幂,如果cap就是2的幂则返回的还是这个数。
对这个方法解释的比较好的一篇博客 。
构造函数 1 2 3 4 5 6 7 8 9 10 11 12 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); }
先检查initialCapacity
和loadFactor
参数值是否正确。
给loadFactor
(负载因子)和 threshold
(扩容阈值)赋值,这里threshold
调用了tableSizeFor
保证了时2的幂,但是没有乘table.length
是因为table
没有在构造函数中初始化还是在put
方法中初始化,同时在put
方法中也会重新对threshold
赋值。
1 2 3 public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); }
这个方法就是调用了上面的方法,只不过loadFactor
给了默认值0.75
。
1 2 3 public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; }
这个方法没有任何参数,代表initialCapacity
和loadFactor
都使用默认值,分别为16
和0.75
。
1 2 3 4 public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); }
这个方法给loadFactor
赋默认值0.75
,然后调用putMapEntries
方法将参数传进来的Map
元素全部加入到table
中。
putMapEntries
会在下面的常用方法介绍。
常用方法 与红黑树相关(TreeNode)的方法会在后面单独介绍 。
get 1 2 3 4 public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
调用getNode
方法传入key
和key
的hash
值,如果为null
直接返回null
否则返回节点的value
值。
getNode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 ) { 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 ; }
先检查table
是否为空,长度是否大于0。
使用table.length - 1
和hash
值进行位与运算,得出在table
上具体位置,将该索引位置的节点赋值给first
节点,检查该索引位置是否为空。
检查first
节点的hash
值和key
是否和入参的一样,如果一样则first
为目标节点,直接返回first
节点。
如果first
下一节点不为空,先判断first
节点是否是红黑树节点,是则调用getTreeNode
查找节点,否则遍历普通链表节点查找。
如果为找到直接返回null
。
put 1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
调用putVal
方法,参数的第一个布尔值:true
代表如果找到节点不替换值,并返回老值,节点为null
时还是会替换,false
代表找到节点会用新值替换老值,并返回老值;第二个是给LinkedHashMap
使用,这里不多介绍。
putVal 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> 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<K,V>)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 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; }
检查table
是否为空或者length
等于0,如果是则调用resieze
方法进行初始化。
通过hash
值计算索引位置,将该索引位置的头节点赋值给p节点,如果该索引位置节点为空则使用传入的参数新增一个节点并放在该索引位置。
判断p节点的key
和hash
值是否跟传入的相等,如果相等,则p节点即为要查找的目标节点,将p节点赋值给e节点。
如果p节点不是目标节点,则判断p节点是否为TreeNode
,如果是则调用putTreeVal
方法查找目标节点。
p为普通链表节点,则调用普通链表的方法进行查找,并定于变量binCount
来统计链表的节点数。
如果p的next
节点为空时,则代表找不到目标节点,则新增一个节点插入链表尾部,并检查链表节点数是否超过8个,如果超过则调用treeifyBin
将链表转成红黑树。
如果遍历的e节点存在hash
值和key
值都与传入的相同,则e节点即为目标节点,跳出循环。
如果e节点不为空,则代表目标节点存在,使用传入的value
覆盖该节点的value
,并返回oldValue
。
如果插入节点后节点数超过阈值,则调用resize
方法进行扩容。
##resize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1 ; } else if (oldThr > 0 ) newCap = oldThr; 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; @SuppressWarnings ({"rawtypes" ,"unchecked" }) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) newTab[e.hash & (newCap - 1 )] = e; 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 ) { if (loTail == null ) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null ) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
如果老表的容量大于0,判断老表的容量是否超过最大容量值:如果超过则将阈值设置为Integer.MAX_VALUE
,并直接返回老表(此时oldCap * 2
比Integer.MAX_VALUE
大,因此无法进行重新分布,只是单纯的将阈值扩容到最大);如果老容量*2小于最大容量并且老容量大于等于默认容量16,就将新的阈值设置为老阈值的2倍。
如果老表的容量为0,老表的阈值大于0,这种情况是穿了容量的new
方法创建的空表,将新表的阈值设置为老表的阈值(这种情况发生在新创建的HashMap
第一次put
时,该HashMap
初始化的时候传了初始容量,由于HashMap
并没有capacity
变量来存放容量值,因此传进来的初始容量时存放在threshold
变量上),因此此时来表threshold
的值就是我们要创建的HashMap
的capacity
,所以将新表的容量设置为老表的阈值。
如果老表的容量为0,老表的阈值为0,这种情况是没有传容量的new方法创建的空表,将阈值和容量设置为默认值。
如果新表的阈值为空,则通过新的容量*负载因子获得阈值(这种情况时初始化的时候传了初始容量,跟第2点相同情况,或者初始容量设置的太小导致老表的容量没有超过16导致的)。
将当前阈值设置刚计算出来的新的阈值,定义新表,容量为刚计算出来的新容量,将当前的表设置为新定义的表。
如果老表不为空,则需遍历所有节点,将节点赋值给新表。
将老表上索引为j的头节点赋值给e,并将老表上索引为j的节点设置为空。
如果e的next
节点为空,则代表老表的该位置只有1个节点,通过hash
值计算新表的位置,直接将该节点放在新表的位置上。
如果e的next
节点不为空,并且e为TreeNode
,则调用split
方法进行hash
分布。
如果e的next
节点不为空,并且e为普通的链表节点,则进行普通的hash
分布。
如果e的hash
值与老表的容量(为一串只有1个为2的二进制数,例如16位0000 0000 0001 0000)进行位与运算为0,则说明e节点扩容后的索引位置跟来表的索引位置一样,进行链表拼接操作;如果loTail
为空,代表该节点为第一个节点,则将loHead
赋值为该节点;否则将节点添加在loTail
后面,并将loTail
赋值为新增的节点。
如果e的hash
值与老表的容量(为一串只有1个为2的二进制数,例如16位0000 0000 0001 0000)进行位与运算为1,则说明e节点扩容后的索引位置:老表的索引位置+oldCap,进行链表拼接操作;如果hiTail
为空,代表该节点为第一个节点,则将hiHead
赋值为该节点;否则将节点添加在hiTail
后面,并将hiTail
赋值为新增的节点。
老表节点重新hash
分布在新表结束后,如果loTail
不为空(说明老表的数据有分布到新表上原索引位置的节点),则将最后一个节点的next
设为空,并将新表上原索引位置的节点设置为对应的头节点;如果hiTail
不为空(说明老表的数据有分布到新表上原索引+oldCap位置的节点),则将最后一个节点的next
设为空,并将新表上索引位置为原索引+oldCap的节点设置为对应的头节点。
返回新表。
putAll 1 2 3 public void putAll (Map<? extends K, ? extends V> m) { putMapEntries(m, true ); }
调用putMapEntries
方法
putMapEntries 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 final void putMapEntries (Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0 ) { if (table == null ) { 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<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false , evict); } } }
remove 1 2 3 4 5 public V remove (Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null , false , true )) == null ? null : e.value; }
调用removeNode
方法传入key
和key
的hash
值,如果为null
直接返回null
否则返回节点的value
值。
removeNode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 ) { 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 ); } } 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 ; }
判断table
不为空,table
长度大于0,table
通过hash
计算索引位置的头节点不为空,否则直接返回null
。
判断头节点是否就是要删除的节点,如果是将p节点赋值给node
节点。
头节点不是要要删除节点,判断p节点是否是TreeNode
节点,如果是调用getTreeNode
方法寻找对应节点。
p为普通链表节点,则调用普通链表的方法遍历进行查找对应节点。
如果寻找到目标节点,判断是否是TreeNode
节点,如果是调用removeTreeNode
方法删除节点;不是TreeNode
节点,判断要寻找的节点是否是头节点,如果是直接将p节点的next节点放在索引位置上;否则为普通链表节点,直接将p节点的next指针指向要删除节点的下一节点。
红黑树(TreeNode)相关方法 getTreeNode 1 2 3 final TreeNode<K,V> getTreeNode (int h, Object k) { return ((parent != null ) ? root() : this ).find(h, k, null ); }
使用根节点调用find
方法。
find
从调用此方法的节点开始查找,通过hash值和key找到对应的节点。
此处是红黑树的遍历,红黑树是特殊的自平衡二叉查找树。
平衡二叉数的特点:左节点 < 根节点 < 右节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 ; }
将p节点赋值为调用此方法的节点,即为红黑树根节点。
从p节点开始向下遍历。
如果传入的hash
值小于p节点的hash
值,则往p节点的右边遍历。
如果传入的hash
值大于p节点的hash
值,则往p节点的右边遍历。
如果传入的hash
和key
值等于p节点的hash
值和key
值,则p节点为目标节点,返回p节点。
如果p节点的左节点为空,则向右遍历。
如果p节点的右节点为空,则向左遍历。
将p节点与k进行比较。如果传入的key
所属的类实现了Comparable
接口,则将k跟p节点的key
进行比较(kc实现了Comparable
接口,因此通过kc的比较方法进行比较),并将比较结果赋值给dir
,如果dir < 0
则代表k < pk
,则向p节点的左边遍历(pl);否则,向p节点的右边遍历(pr)。
key
所属类没有实现Comparable
接口,因此直接向右遍历。
pr.find(h, k, kc)
为空,因此直接向左遍历。
putTreeVal 红黑树的put操作,红黑树插入会同时维护原来的链表属性,即原来的next属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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 ; } } }
查找根节点,索引位置的头节点不一定为红黑树的根节点。
将根节点赋值给p节点,开始进行查找。
如果传入的hash
值小于p节点的hash
值,将dir
赋值为-1,代表向p的左边查找树。
如果传入的hash
值大于p节点的hash
值,将dir
赋值为1,代表向p的右边查找树。
如果传入的hash
值和key
值等于p节点的hash
值和key
值,则p节点即为目标节点,返回p节点。
如果k所属的类型没有实现Comparable
接口 或者 k和p节点的key
相等;如果searched
为false
,从p节点的左节点和右节点分别调用find
方法进行查找,如果查找到目标节点则返回;否则使用定义的一套规则来比较k和p节点的key
的大小,用来决定向左还是向右查找。
dir < 0
则向p左边查找,否则向p右边查找,如果为null
,则代表该位置即为x的目标位置。
创建新的节点,其中x的next
节点为xpn
,即将x节点插入xp
与xpn
之间。
调整x、xp、xpn之间的属性关系。
进行红黑树的插入平衡调整。
tieBreakOrder 1 2 3 4 5 6 7 8 9 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; }
用于不可比较或者hashCode
相同时进行比较的方法,只是一个一直的插入规则,用来维护定位的等价性。
treeifyBin 将链表节点转换为红黑树节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 final void treeifyBin (Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); 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); } }
如果table
为空或者table
的长度小于64,调用resize
方法进行扩容。
根据hash
值j计算索引值,将该索引值位置的节点赋值给e,从e开始遍历该索引位置的链表。
将链表节点转红黑树节点。
如果是第一次遍历,将头节点赋值给hd。
如果不是第一次遍历,则处理当前节点的prev
属性和上一节点的next
属性。
将p节点赋值给t1,用于在下一次循环中作为上一节点进行一些链表的关联操作(p.prev = t1 和 t1.next = p)。
将table
该索引位置赋值新转的TreeNode
的头节点,如果该节点不为空,则以头节点(hd)为根节点,构建红黑树。
treeify 构建红黑树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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); }
将调用此方法的节点赋值给x,以x作为起点,开始进行遍历。
如果还没有根节点,则将x设置为根节点。
如果当前节点x不是根节点,则从根节点开始查找属于该节点的位置。
如果x节点的hash
值小于p节点的hash
值,则将dir
赋值为-1,代表向p的左边查找。
如果x节点的hash
值大于p节点的hash
值,则将dir
赋值为1,代表向p的右边查找。
走到这代表x的hash
值和p的hash
值相等,则比较key
值。
dir <= 0
则向p左边查找,否则向p右边查找,如果为null
,则代表该位置即为x的目标位置。
x和xp节点的属性设置。
进行红黑树的插入平衡(通过左旋、右旋和改变节点颜色来保证当前数符合红黑树要求)。
如果root
节点不再table
索引位置的头节点,则将其调整为头节点。
moveRootToFront 将root放到头节点的位置,如果当前索引位置的头节点不是root节点,则将root的上一节点和下一节点进行关联,将root放到头节点的位置,原头节点放在root的next节点上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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) ; } }
检查root是否为空,table是否为空,table的length是否大于0。
计算root节点的索引位置。
如果该索引位置的头节点不是root节点,则该索引位置的头节点替换为root节点。
检查树是否正常。
checkInvariants 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 ; }
将传入的节点作为根节点,遍历所有节点,检查节点的合法性,主要是保证树符合红黑树的规则。
split 扩容后,红黑树的hash分布,只可能存在于两个位置:原索引位置、原索引位置 + oldCap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 final void split (HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this ; 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 ) 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); } } }
以调用此方法的节点开始,遍历整个红黑树节点。
如果e的hash
值与老表的容量进行与运算为0,则扩容后的索引位置跟老表的索引位置一样。
如果e的hash
值与老表的容量进行与运算为1,则扩容后的索引位置为:老表的索引位置 + oldCap
。
如果原索引位置的节点不为空,如果节点个数小于等于6则将红黑树转为链表结构,否则将原索引位置的节点设置为对应的头节点,如果hiHead
不为空,则代表原来的红黑树已经被改变,需要重新构建新的红黑树。
如果索引位置为原索引+oldCap的节点不为空,如果节点个数小于等于6则将红黑树转为链表结构,否则将原索引位置+ oldCap
的节点设置为对应的头节点,如果hiHead
不为空,则代表原来的红黑树已经被改变,需要重新构建新的红黑树。
untreeify 将红黑树节点转为链表节点,当节点<=6时会被触发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; }
从调用该方法节点,即链表的头节点开始遍历,将所有节点转换为链表节点。
调用replacementNode
方法构建链表节点。
如果t1为null,代表是第一个节点,将hd赋值为该节点,否则否则将尾节点赋值给p。
每次将尾节点指向当前节点,即为节点。
返回转换后的链表的头节点 。
remove 红黑树的节点移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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 || (movable && (root.right == null || (rl = root.left) == null || rl.left == null ))) { tab[index] = first.untreeify(map); 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 ) s = sl; boolean c = s.red; s.red = p.red; p.red = c; 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; 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) { 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); }