HashMap源码解析及面试题

5 阅读14分钟

HashMap源码解析

  1. HashMap的成员变量和构造方法(本文源码基于JDK 1.8)
public class HashMap<K, V> {

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

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

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

    /**
     * Hashmap数组节点上的链表转为红黑树的的第一个条件,链表节点数达到8个
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 链表节点数小于6个时,从红黑树转换为链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 链表转为红黑树的第二个条件,与TREEIFY_THRESHOLD对应,最小的链转树的数组大小。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * Node<K,V>数组
     */
    transient Node<K, V>[] table;

    /**
     * key-value映射的个数
     */
    transient int size;

    /**
     * 记录hashmap节点修改次数,这个字段用于保障在for循环遍历hashmap时,
     * 不可以对hashmap里面的数据发生结构性改变,如删除其中一个key-value,
     * 会导致fast-fail【抛出ConcurrentModificationException】,
     * 正确的方式是使用迭代器遍历删除。
     */
    transient int modCount;

    /**
     * 阈值 =(容量 * 加载因子)
     */
    int threshold;

    /**
     * 加载因子
     */
    final float loadFactor;

    /**
     * 创建一个初始容量为initialCapacity,加载因子为loadFactor的空HashMap
     */
    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 ,加载因子为 0.75 的空 HashMap 
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    // 创建一个初始容量 16 ,加载因子为 0.75 的空 HashMap
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    // 创建一个 HashMap ,其加载因子为 0.75 , key 和 value 为 m 的 key 和 value
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

}

我们最常使用的是无参的构造方法,这个构造方法里面给 loadFactor 赋了初始值 0.75。在你调用 put() 方法往 HashMap 中插入元素的时候会调用 resize() 方法,在这里会初始化 threshold,table 数组也是在 resize() 方法中初始化的,后面我们会提到。

如果你使用带 initialCapacity 参数的构造方法,里面会调用 tableSizeFor(initialCapacity) 来计算 threshold ,代码如下:

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 的幂。它的实现步骤就是把最高位的 1 传给低位,比如传入 cap=9 ,那么 n = 0x1000,n 右移 1 位与自己进行或运算后 n = 0x1100,n 右移 2 位与自己进行或运算后 n = 0x1111,最终通过 tableSizeFor() 计算出来的结果就是 16。

  1. HashMap的Node节点

HashMap 是链表的时候,使用 Node 节点来存储键值对, Node 是 HashMap 的静态内部类,有 hash , key , value , next 四个属性,代码如下:

public class HashMap<K,V>{

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

转成红黑树后使用 TreeNode 来存储键值对,代码如下:

public class HashMap<K,V>{

    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent; // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev; // needed to unlink next upon deletion
        boolean red;
        //构造方法
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
    } 
}
  1. HashMap 中的 put() 方法
public class HashMap<K, V> {

    /**
     * 将 value 与 key 在 map 中关联起来,如果 map 中存在与 key 关联的 value ,
     * 旧 value 将被替换,返回值为旧 value
     */
    public V put(K key, V value) {
        // 通过 hash(key) 取值
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K, V>[] tab;
        Node<K, V> p;
        int n, i;
        //初始化tab=table,初始化n=tab.length,table为null时执行resize()初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //通过(n - 1) & hash 拿到数组下标i,p为tab[i]对应的节点,
        //也是链表的头节点,如果为null,新建Node
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //如果p不为null
        else {
            Node<K, V> e;
            K k;
            //如果头结点的hash和key与插入节点相等
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果头结点是红黑树
            else if (p instanceof TreeNode)
                //调用红黑树的putTreeVal()方法
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            //如果头结点是链表
            else {
                for (int binCount = 0; ; ++binCount) {
                    //p.next赋值给e,p.next == null即表示遍历到了链表尾部
                    if ((e = p.next) == null) {
                        //在链表尾部插入新Node
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //链表长度为8时,调用treeifyBin()方法
                            treeifyBin(tab, hash);
                        //退出循环 
                        break;
                    }

                    //节点的hash和key与插入节点相等
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        //退出循环
                        break;

                    //遍历 
                    p = e;
                }
            }

            //map中已经存在对应的key
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //替换旧value
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                //返回旧value
                return oldValue;
            }
        }
        ++modCount;
        //个数大于阈值
        if (++size > threshold)
            resize(); //扩容
        afterNodeInsertion(evict);
        return null;
    }


    final void treeifyBin(Node<K, V>[] tab, int hash) {
        int n, index;
        Node<K, V> e;
        //如果数组容量小于64,仍然采取resize()扩容
        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);
        }
    }


    Node<K, V> newNode(int hash, K key, V value, Node<K, V> next) {
        return new Node<>(hash, key, value, next);
    }

    //空方法
    void afterNodeInsertion(boolean evict) {
    }

    final Node<K, V>[] resize() {
        Node<K, V>[] oldTab = table;
        //旧的数组的容量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //旧的阈值
        int oldThr = threshold;
        int newCap, newThr = 0;
        //旧的容量大于0
        if (oldCap > 0) {
            //如果旧的容量已经超过MAXIMUM_CAPACITY = 0x40000000,
            //则不进行扩容操作,将当前阈值赋值为Integer.MAX_VALUE
            if (oldCap >= MAXIMUM_CAPACITY) {
                //Integer.MAX_VALUE = 0x7fffffff
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //newCap是oldCap的2倍,newThr是oldThr的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果旧的容量等于0,旧的阈值大于0
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //旧的容量和阈值都等于0 
        else { // zero initial threshold signifies using defaults
            //调用HashMap的默认构造函数走这里初始化
            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数组 
        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;
                    //如果是红黑树,执行split()方法 
                    else if (e instanceof TreeNode)
                        ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                    //链表重新hash 
                    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;
    }

    /**
     * 将node结点拆分成lower tree bin和upper tree bin,
     * 如果发现太小会转回链表
     */
    final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
        TreeNode<K, V> b = this;
        // Relink into lo and hi lists, preserving order
        //重新链接到 lo 和 hi 两个链表,保持以前的顺序
        TreeNode<K, V> loHead = null, loTail = null;
        TreeNode<K, V> hiHead = null, hiTail = null;
        //lc是lo链表的元素个数,hc是hi链表的元素个数
        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;
            //bit为容量,添加到lo链表
            if ((e.hash & bit) == 0) {
                if ((e.prev = loTail) == null)
                    loHead = e;
                else
                    loTail.next = e;
                loTail = e;
                ++lc;
            }
            //添加到hi链表
            else {
                if ((e.prev = hiTail) == null)
                    hiHead = e;
                else
                    hiTail.next = e;
                hiTail = e;
                ++hc;
            }
        }

        //如果lo链表的元素个数小于等于6,退化成链表 
        //并插入到新数组 tab[index] 的位置上,index是当前红黑树所在旧数组坐标
        if (loHead != null) {
            if (lc <= UNTREEIFY_THRESHOLD)
                tab[index] = loHead.untreeify(map);
            else {
                tab[index] = loHead;
                if (hiHead != null) // (else is already treeified)
                    loHead.treeify(tab);
            }
        }

        //如果hi链表的元素个数小于等于6,退化成链表 
        //并插入到新数组 tab[index + bit] 的位置上,index是当前红黑树所在旧数组坐标
        if (hiHead != null) {
            if (hc <= UNTREEIFY_THRESHOLD)
                tab[index + bit] = hiHead.untreeify(map);
            else {
                tab[index + bit] = hiHead;
                if (loHead != null)
                    hiHead.treeify(tab);
            }
        }
    }
}

通过以上代码可以得出以下结论:

  • table 的初始化在 resize() 方法中。
  • HashMap 在执行 put() 方法时,如果发现链表长度大于或等于 8 ,并且数组长度大于或等于 64 时,会将链表转成红黑树。
  • 如果红黑树的TreeNode个数小于或等于 6 ,会从红黑树转回链表。
  1. HashMap 中的 remove() 方法
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {

    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;
            //如果头结点就是要remove的节点
            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!=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]指向下一个节点
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

}


static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {


    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();
        //如果红黑树根(root)为空,或者根的右子树为空,或者根的左子树为空,
        //或者根的左子树的左子树(root.left.left)为空, 
        //都会发生红黑树退化成链表 
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map); // too small
            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) // find successor
                s = sl;
            boolean c = s.red;
            s.red = p.red;
            p.red = c; // swap colors
            TreeNode<K, V> sr = s.right;
            TreeNode<K, V> pp = p.parent;
            if (s == pr) { // p was s's direct parent
                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) { // 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);
    }
}

由以上代码可以得出以下结论:

  • HashMap在执行remove()方法时,如果发现红黑树根为空、根的左子树或右子树为空,或根的左子树的左子树为空,都会发生红黑树退化成链表的情况。

HashMap面试题

  1. 讲讲 HashMap 的数据结构和底层原理。

HashMap 是由数组和链表组合成的数据结构(Java 8 加入了红黑树),里面存储了 Key-Value 键值对,在 Java 7 叫 Entry,在 Java 8 中叫 Node 。在插入的时候会根据 key 的哈希算法来计算插入的位置,但是有一定概率计算出来的哈希值是一样的,值一样的情况我们就放到一个链表上。

  1. 数据在插入链表的时候是怎么插入的?

在插入之前先对插入的节点的key通过hash(key)取值,然后通过hash(key) & 数组长度(length -1)获取它在数组中的下标i,最后在Node[]数组中查找Node[i],如果Node[i]为null,直接插入;如果有值,遍历对应位置的链表,如果有Node的hash值与之相等并且key值也相等,则替换Node的value并返回,否则插入链表的末尾。

Java 8之前是头插法,会插入链表的头部,原有的值就顺序往后推,因为写这个代码的作者认为后插入的值被查找的可能性更大一点,插入头部能提升查找的效率;Java 8之后用尾插法。

  1. 为什么Java 8之后采用尾部插入?

原因是头插法在多线程插入数据的情况下会造成环形链表(Infinite Loop),使用尾插法在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

那是不是意味着Java 8就可以把HashMap用在多线程中呢?

不是的,即使不会出现死循环,但是通过源码可以看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。

  1. 链表是怎么扩容的?扩容因子为什么是0.75?

链表扩容主要分为2步:

  • 扩容:创建一个新的Node空数组,长度是原数组的2倍。
  • ReHash:遍历原Node数组,把所有的Node重新Hash到新数组。
  1. 为什么要重新Hash(rehash)?为什么不直接复制过来?

Hash的公式: index = HashCode(Key) & (Length - 1),扩容后Length已经变了,计算出来的index会发生改变,原来在一个链表下的数据执行rehash后很大概率不会还在一个链表下。

  1. HashMap的默认大小为什么是16?

index的计算公式为:index = HashCode(Key) & (Length- 1),Length初始值为16,16是2的4次方,后续扩容也都是2的指数次幂,16-1的二进制是1111,这样hashcode与之进行&运算就是hashcode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的,这是为了实现均匀分布,最大程度地避免哈希碰撞。同时,在计算机中,位运算比算数运算效率高很多,所以在源码中使用的是DEFAULT_INITIAL_CAPACITY = 1 << 4。

  1. 为什么重写equals()方法的时候需要重写hashCode()方法?

HashMap的put()方法会使用key的hashCode()方法和equals()方法来协同确定元素的位置:先通过hashCode()方法确定在数组中的index,再通过equals()方法来确定在链表上的位置。如果只重写了equals()方法但是没有重写hashCode()方法,会直接执行Object中的hashCode()方法,而Object中的hashCode()方法对比的是两个对象的内存地址,不同的对象返回结果是false,后面的equals()方法也不用执行了,直接返回的结果就是false。

运行如下示例:

public class Test {

    public static void main(String[] args) {

        Map<Person, String> map = new HashMap<>();

        String name = "Jim";
        int age = 20;
        Person person1 = new Person(name, age);
        map.put(person1, name);

        Person person2 = new Person(name, age);
        map.put(person2, name);

        Iterator<Person> keyIterator = map.keySet().iterator();
        while (keyIterator.hasNext()) {
            Person key = keyIterator.next();
            String value = map.get(key);
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }


    static class Person {
        String name;
        int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true; // 引用相等返回 true
            // 如果等于 null,或者对象类型不同返回 false
            if (o == null || getClass() != o.getClass()) return false;
            // 强转为自定义 Person 类型
            Person person = (Person) o;
            // 如果 age 和 name 都相等,就返回 true
            return age == person.age && Objects.equals(name, person.name);
        }
        
        // @Override
        // public int hashCode() {
        //    return Objects.hash(name, age);
        // }
    }
}

打印如下:

Key: com.example.test.Test$Person@610455d6, Value: Jim
Key: com.example.test.Test$Person@511d50c0, Value: Jim

通过以上代码可以看到虽然person1和person2的name和age一样,但是HashMap在插入person2时不会覆盖person1,所以会有2条打印。但是如果把hashCode()方法的注释打开,就只会有一条打印了。

我们经常使用Integer或String作为HashMap的key,这些类里面已经对hashCode()方法和equals()方法进行了重写,所以不需要我们额外处理。但是如果你使用自定义的类作为HashMap的key就需要自己重写hashCode()方法和equals()方法。

  1. HashMap是线程不安全的,怎么解决线程不安全的场景呢?

一般我们使用Collections.synchronizedMap(Map)创建线程安全的map集合、使用HashTable或者ConcurrentHashMap都可以用来解决线程不安全场景,不过出于线程并发度的原因,我都会舍弃前两者使用最后的ConcurrentHashMap,他的性能和效率明显高于前两者。

  1. hash的计算规则?

  2. 为什么引入红黑树?

为了减小查找和插入的时间复杂度,红黑树的引入将原本O(n)的时间复杂度降低到了O(logn)。

  1. 什么时候转换成红黑树,删除元素后会回到链表吗?

HashMap在执行put()方法添加元素时,如果发现链表长度大于或等于8,并且数组长度大于或等于64时,会将链表转成红黑树。

在扩容resize()时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表。

在移除元素remove()时,removeTreeNode()方法会检查红黑树是否满足退化条件,如果红黑树根为空,或者根的左子树/右子树为空,或者根的左子树的左子树为空,都会发生红黑树退化成链表。

感谢与参考:

《我们一起进大厂》系列-HashMap

重写 equals 时为什么一定要重写 hashCode?