JDK1.8 -- HashMap源码阅读

    xiaoxiao2022-07-07  158

    文章目录

    1.HashMap的基本思想简述2.成员变量介绍3.构造函数介绍4.其他方法介绍5.红黑树存储结构介绍

    1.HashMap的基本思想简述

    HashMap是通过数组+链表+红黑树的方式存储键值对,他会根据要存储的键值对的hash值来确定该键值对在数组中的插入位置,如果该位置已经有元素,则插入到该元素的后面,当链表长度到达8并且数组的长度超过64时,链表会自动转化成为红黑树存储,这是为了保证查找效率,红黑树的查找效率为O(logn)。HashMap是无序的。HashMap可以接受null的键值。

    2.成员变量介绍

    DEFAULT_INITIAL_CAPACITY:默认初始容量为16,这里需要说明hashMap的容量必须要为2的幂数,这是因为以下几点原因(欢迎补充): 我们在计算新的键值对插入的位置时是根据hash值来确定,具体的公式为如下所示,其中n为map的容量,因此减1之后为若干个连续的1,与hash值做&运算时,不会超过map的容量&运算快于取模运算若n满足是2的幂数,则满足了 (n - 1) & hash = hash % n 这个公式 DEFAULT_LOAD_FACTOR:默认负载因子,当map的size超过容量*负载因子时,map会自动扩容为原来的两倍,这个负载因子是经过许多试验的出来的最优值,基本不用修改。entrySet:键值对集合loadFactor:负载因子MAXIMUM_CAPACITY:最大容量 为1 << 30TREEIFY_THRESHOLD:链表转化为红黑树的最小长度UNTREEIFY_THRESHOLD:红黑树转化为链表的最小长度modCount:实现fail-Fast机制size:map中的元素的个数table:存储元素的数组,数据类型为Node<k, v> static class Node<K,V> implements Map.Entry<K,V> { final int hash; //键值对的hash值 final K key; //键 V value; //值 Node<K,V> next; //因为要通过链表来解决hash碰撞,所以存在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; } //Node类型的hashCode算法 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; } } threshold:他的值为 loadFactor*Capacity,当size超过这个值时,map就会自动扩容

    3.构造函数介绍

    public HashMap(int initialCapacity, float loadFactor):通过给定的容量和负载因子来创建一个map,查看一下源码我们注意到map的最大容量是MAXIMUM_CAPACITY,如果你给定的初始容量超过了这个值,则map会自动转化为MAXIMUM_CAPACITY,其次我们注意到this.threshold = tableSizeFor(initialCapacity) 这段代码,我们在前面提到threshold的值是capacity*loadFactor,这里明显与我们提到的不符,(tableSizeFor是将给定的初始容量转化为2的幂数的函数,他返回转化后的初始容量)。其实并不是这样,我们发现这个构造函数并没有初始化我们的table数组,因此在之后的resize函数中会对threshold重新赋值。 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); } /* 这个函数的操作就是将输入的cap的二进制最高位的1之后的所有位数全部转化为1,可以自己动手试试, 关于 n = cap - 1的操作,我认为是防止cap本身就是2的幂,因为如果cap本身是2的幂,经过函数转化 出来的容量就是原来容量的两倍,这并不符合我们创建这个函数的初衷,我们的目的是将不是2的幂数的容 量转化为2的幂数,对于本身就是2的幂数的容量则不作处理 */ 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; } public HashMap(int initialCapacity):通过给定的初始容量创建map,它会自动调用第一种构造函数public HashMap():创建一个空map public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(Map<? extends K, ? extends V> m):通过给定的map创建一个新的map,我们查看这个putMapEntries函数 public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // 初次创建时,table没有初始化,执行以下代码 float ft = ((float)s / loadFactor) + 1.0F; // 根据给定map来计算新map的容量 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); } } }

    4.其他方法介绍

    size():返回map的sizeclear():清空mapclone():返回一个深拷贝mapisEmpty():根据size判断,map是否为空public V put(K key, V value):将给定的键值对存储在map中 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { //键的hash值算法, int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); /*这里只将key的hash值的高十六位参与运算,称为扰动函数, 用于减小hash碰撞,这里不做详解,感兴趣的话可以自己查资料。*/ } 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) //判断table是否没有初始化,如果没有进行初始化,则调用resize()完成table的初始化 n = (tab = resize()).length; // n为table的长度 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)))) //与存在的元素相等,则不作处理,返回该元素的值,“||”允许key为null 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) // 这里建议是因为在table中的元素也是链表的一元,所以要减一,如果超过8则要转化成为红黑树 treeifyBin(tab, hash); //转化成红黑树 break; //退出 } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 找到相同的元素,则退出,返回其value break; p = e; } } if (e != null) { // 即原来的map中存在该key值 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //为LinkedHashMap服务 return oldValue; } } ++modCount; if (++size > threshold) //检查是否查过treshold值 resize(); afterNodeInsertion(evict); //为LinkedHashMap服务 return null; } public V get(Object key) : 返回给定键的值,如果给定键不存在则返回null public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } 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 && //如果第一个Node的key和hash值相等,则返回该Node ((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; } containsKey(Object key):确定map中是否存在该key,返回布尔值,与get()方法的实现方法一致。 containsValue(Object value):与上一个方法类似。 public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):尝试计算用于指定键和其当前映射的值的映射(或 null如果没有当前映射),如果映射得到的新value为null,则删除此键值对,若key不存在且新value值部位null,则插入该键值对。 public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { if (remappingFunction == null) throw new NullPointerException(); int hash = hash(key); Node<K,V>[] tab; Node<K,V> first; int n, i; int binCount = 0; TreeNode<K,V> t = null; Node<K,V> old = null; if (size > threshold || (tab = table) == null || //检查是否需要扩容 (n = tab.length) == 0) n = (tab = resize()).length; if ((first = tab[i = (n - 1) & hash]) != null) { //分为红黑树和链表两种情况寻找Node if (first instanceof TreeNode) old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key); else { Node<K,V> e = first; K k; do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { old = e; break; } ++binCount; } while ((e = e.next) != null); } } V oldValue = (old == null) ? null : old.value; //得到旧value值 V v = remappingFunction.apply(key, oldValue); //计算新的value值 if (old != null) { //如果对应的键值对存在 if (v != null) { old.value = v; //修改value值 //如果新的value值不为空,则修改value afterNodeAccess(old); } else removeNode(hash, key, null, false, true); //如果新的value值为空,则删除该键值对 } else if (v != null) { if (t != null) //如果该key值不存在,且为红黑树 t.putTreeVal(this, tab, hash, key, v); //如果 else { tab[i] = newNode(hash, key, v, first); //如果该key值不存在且为链表,则从头部插入 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } ++modCount; ++size; afterNodeInsertion(true); } return v; //返回新的value值或者null } public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction):与compute实现方法类似,但是如果指定key值存在,则函数映射不会进行计算,这某种程度上比 put() 性能更好public V computeIfPrsent(K key, Function<? super K, ? extends V> mappingFunction):与上述类似不在重复。V remove(Object key):移除指定的键值对。 public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } 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 && //对第一个Node进行核对 ((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); //得到对应的Node else { do { //得到对应的Node 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); //this就是node else if (node == p) //删除Node操作 tab[index] = node.next; else p.next = node.next; //删除Node操作 ++modCount; --size; afterNodeRemoval(node); return node; //返回被删除的Node } } return null; //若不存在Node则返回Null } resize(): 详情请看jdk1.8 HashMap源码分析(resize函数) 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) { //如果table已经初始化 if (oldCap >= MAXIMUM_CAPACITY) { //检查是否超出最大容量 threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //若翻倍后不超出最大容量,则将容量翻倍,同时threshold也翻倍 oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // 翻倍 } else if (oldThr > 0) // 如果table没有被初始化,但是有了threshold值,则将threshold当成新容量,建议和上文中的构造函数一起看 newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { //计算新的threshold值 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,或者扩容 table = newTab; //将没有扩容前的table中的内容根据hash值重新分配到新的table中 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; //删除旧table中的Node if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //根据hash值重新分配位置 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order 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; }

    5.红黑树存储结构介绍

    下文内容部分参考自: 【Java入门提高篇】Day25 史上最详细的HashMap红黑树解析

    Java源码阅读笔记之TreeNode JDK8:HashMap源码解析:TreeNode类的moveRootToFront方法

    红黑树的特点:

    红黑树的根结点一定是黑色红色结点的子节点一定是黑色一个结点的左右子节点的颜色一定相同每个结点到其所有子节点的路径上黑色结点数一定相同

    红黑树的插入规则:

    1.如果插入的结点是根结点,则把颜色改为黑色即可2.如果插入的结点的父节点是黑色,则不操作3.如果插入结点的父节点和祖父节点都存在,且父节点为祖父节点的左节点,则分为两种情况讨论 如果叔父节点的颜色为红色,则将父节点和叔父节点的颜色改为黑色,祖父节点改为红色如果叔父节点的颜色为黑色或者叔父节点为null,则分两种情况讨论 如果 插入节点为父节点的左节点,将父节点改为黑色,祖父节点改为红色,祖父节点右旋如果插入节点为父节点的右节点,将父节点左旋 4.如果插入节点父节点和祖父节点都存在,且父节点为祖父节点的右节点,则分为两种情况 如果叔父节点的颜色为红色,则将父节点和叔父节点的颜色改为黑色,祖父节点改为红色如果叔父节点的颜色为黑色,或者叔父节点为null 如果插入节点为父节点的左节点,则将父节点右旋如果插入节点为父节点的右节点,则将父节点的颜色改为黑色,祖父节点的颜色改为红色,然后祖父节点左旋 直到到达情况1或者情况2终止。

    红黑树结点的删除 – 三种情况:

    被删除结点没有子结点,直接删除被删除结点有左节点或者右节点被删除结点同时有左节点和右节点,此时遵循二叉搜索树的删除方法,寻找被删除结点的后继结点,将key值对换,则问题转换为删除后继结点,当然后继结点是最多只有一个子节点的,那么就把问题转化为第二种情况了。之后请参考上面的博文!!![笔者画图太丑]

    红黑树的查找效率:

    O(n)

    红黑树的删除效率:

    O(n)

    treeNode:注意为双向链表!继承自LinkedHashMap.Entry<K,V>

    left:左节点right:右节点prev:链表中的上一个节点next:链表中的下一个节点parent:父节点red:红黑树的颜色 false为黑色,true为红色。

    rotateLeft()/rorateRight():左转和右转,用于平衡红黑树。

    treeify(Node<K,V>[] tab):void:将table中的结点转化为红黑树,注意链表转化为红黑树时,红黑树结点还是保存着next属性,既用于将链表转化为红黑树,有用于将红黑树转化为链表。

    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; //根据hash值判断树节点的位置 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); } Node<K,V> untreeify(HashMap<K,V> map):将table中的元素从红黑树转化为链表。 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) { //replacementNode new一个Node,根据q的k,v,h,next Node<K,V> p = map.replacementNode(q, null); if (tl == null) //第一次运行时执行 hd = p; else tl.next = p; tl = p; } return hd; } TreeNode<K,V> root():返回红黑树的根结点,注意当链表转化为红黑树之后,红黑树的根结点并不一定是table数组中的元素,需要通过moveRootToFront()方法将root结点转化为table中的元素<K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root): static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { // 根节点不为空 并且 HashMap的元素数组不为空 int index = (n - 1) & root.hash; // 根据根节点的Hash值 和 HashMap的元素数组长度 取得根节点在数组中的位置 TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; // 首先取得该位置上的第一个节点对象 if (root != first) { // 如果该节点对象 与 根节点对象 不同 Node<K,V> rn; // 定义根节点的后一个节点 tab[index] = root; // 把元素数组index位置的元素替换为根节点对象 TreeNode<K,V> rp = root.prev; // 获取根节点对象的前一个节点 if ((rn = root.next) != null) // 如果后节点不为空 ((TreeNode<K,V>)rn).prev = rp; // root后节点的前节点 指向到 root的前节点,相当于把root从链表中摘除 if (rp != null) // 如果root的前节点不为空 rp.next = rn; // root前节点的后节点 指向到 root的后节点 if (first != null) // 如果数组该位置上原来的元素不为空 first.prev = root; // 这个原有的元素的 前节点 指向到 root,相当于root目前位于链表的首位 root.next = first; // 原来的第一个节点现在作为root的下一个节点,变成了第二个节点 root.prev = null; // 首节点没有前节点 } /* * 这一步是防御性的编程 * 校验TreeNode对象是否满足红黑树和双链表的特性 * 如果这个方法校验不通过:可能是因为用户编程失误,破坏了结构(例如:并发场景下);也可能是TreeNode的实现有问题(这个是理论上的以防万一); */ assert checkInvariants(root); } } TreeNode<K,V> find(int h, Object k, Class<?> kc):用于寻找指定的结点并返回。 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; //对比hash值 if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; //hash值相同时 //判断是否为结果 else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; //hash值相同,一侧节点为空则直接进入 else if (pl == null) p = pr; else if (pr == null) p = pl; //使用HashMap定义的方法判断结果在哪侧 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; } TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v):将指定的结点插入到红黑树中,在阅读源码之前建议先查看之前给出的将结点插入到红黑树中的几种case和方法。 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; //通过hash和key判断 //hash值不相同 if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; //hash值相同 //key已存在,不能存在相同的key else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; //不同key的hash值相同,需要用到HashMap定义的判断方法 //dir决定寻找的方向 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); //用key的原hashCode()方法得出哈希值 } //找到空节点,插入新节点 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; } } } balanceInsertion():参考文前给出的连接balanceDeletion():参考文前给出的链接,红黑树的删除平衡操作过于复杂,笔者参考了许多博客,对自己的见解也没有很大把握,就不在这里献丑了。void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable):删除指定的节点,建议翻看前文给出的红黑树删除的逻辑方法。 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { // section 1:通过prev和next删除当前节点 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; // section 2:当节点数量小于7时转换成链栈的形式存储 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; } // section 3:判断当前树节点情况 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是s的父节点 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; // section 4:实现删除树节点逻辑 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); }
    最新回复(0)