JDK8 HashMap put() 方法源码分析

2023-11-16

文章目录

一、前置知识

红黑树定义

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 结点是红色或黑色。
  2. 根结点是黑色。
  3. 所有叶子都是黑色。(叶子是NIL结点)
  4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
  5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

二、构造方法

HashMap()

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

HashMap(int initialCapacity, float loadFactor)

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 如果指定的容量大于 1 << 30, 则指定为最大值
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    // 将非2的幂次的容量,指定为2的幂次
    // hashmap的容量是2的幂,如果传入的参数不是2的幂,则会算出比该参数大的最小的2的幂次的数;例如,传入3,则容量4;传入12,则容量16
    this.threshold = tableSizeFor(initialCapacity);
}

tableSizeFor(int cap):计算hashmap初始容量

static final int tableSizeFor(int cap) {
	// 减1之后,用最高位的 1 把后面每一位都变成 1,此时再加1时,就是2的幂次
    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;
}

三、put 方法源码

1. put()

public V put(K key, V value) {
	// 计算 key 的 hash 值
    return putVal(hash(key), key, value, false, true);
}

hash(Object key):计算key的hash值

/**
 * 计算 key.hashCode() 并将散列的高位散布 (XOR) 到低位。
 * 因为该表使用二次方掩码,所以仅在当前掩码以上的位上有所不同的散列集将始终发生冲突。 (在已知的例子中有一组 Float 键在小表中保存连续的整数。)
 * 所以我们应用一个转换来向下传播较高位的影响。在位扩展的速度、效用和质量之间存在权衡。
 * 因为许多常见的哈希集已经合理分布(因此不会从传播中受益),并且因为我们使用树来处理 bins 中的大量冲突,所以我们只是以最便宜的方式对一些移位的位进行 XOR 以减少系统损失,以及合并最高位的影响,否则由于表边界而永远不会在索引计算中使用这些位。
 */
static final int hash(Object key) {
    int h;
    // key hashcode 的值(32位),高16位与低16位按位异或
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2. putVal()

该方法主要考虑以下几种情况:

放值前:

  1. 初始化时,未指定 HashMap 的容量,设置默认容量为 16

存放值时:

  1. 数组节点上没有存任何 key,即没有发生 hash 碰撞
  2. 数组节点的 key 和新 put 的 key 一样
  3. 数组节点是红黑树
  4. 数组节点是链表

放完后:

  1. 检查 hashmap 中所有元素个数,超过 负载因子*容量,则扩容
/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // 访问局部变量而不是类属性的理解:访问栈空间比访问堆空间更快(逃逸分析、栈上分配)
    // tab:数组;p:point,指针,当前访问的节点;n:数组大小;i:k hash值对应的数组下标
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
    	// 初始化时,未指定 HashMap 的容量,容量为 0,需先扩容
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
    	// 数组上的节点中没有存值
        tab[i] = newNode(hash, key, value, null);
    else {
    	// e:hashmap 中存在key和新插入key相同的旧节点
    	// k:旧节点的 key
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 数组节点上的 key 和新插入的 key 一致
            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) // -1 for 1st
                    	// 当第9个插入完成后(此时binCount=7),链表转成红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 遍历链表时发现的相同key的情况
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
            	// 存在旧的值,如果是 put() 方法,就替换,如果是 putIfAbsent() 方法就不替换
                e.value = value;
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
    	// 如果数组中元素个数大于 容量*负载因子,则扩容
        resize();
    afterNodeInsertion(evict);
    return null;
}

通过 hash 计算数组下标

通过 hash 值计算对应的数组下标:(n - 1) & hash

n 是数组大小,是 2 的幂,对应的二进制就是最高位是 1,其余位全是 0
在这里插入图片描述

我对该算法的理解:

  1. n-1 后,每个位上的值必须都是 1,所以 n 必须是 2 的幂
  2. 因为该算法只是取 int 的低位,所以在计算 hash 值的时候,将高16位与低16位进行异或运算,减少高位不同而低位相同产生的 hash 碰撞

3. resize():扩容

/**
 * 初始化或表大小 * 2。
 * 如果为空,则在最大值中保持的初始容量目标。
 * 否则,数组大小*2,hashmap 中每个元素必须保持在相同的索引中或者在新表的2的幂的偏移中
 */
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;
        }
        // 容量*2
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 扩容后容量<最大容量且旧容量>=16时,扩展门槛*2
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 设置默认容量大小是 16,扩容门槛(容量*负载因子)是12
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        // 扩容门槛为 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 { // preserve order
                    // 数组中是链表的情况
                    // 高位和低位的理解:扩容后,原先相同hash的key,只会被分到新数组的两个位置,大的位置叫高位,小的位置叫低位
                    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;
}

扩容时计算数组下标

在这里插入图片描述

4. treeifyBin(tab, hash):链表转为红黑树

/**
 * 替换给定哈希的索引处所有链表节点,除非表太小,在这种情况下会扩容。
 */
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)
        // 如果数组没有被初始化或者数组长度小于64,扩容
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // hd(head):头结点 tl(tail):尾结点
        TreeNode<K,V> hd = null, tl = null;
        do {
            // 将 Node 结点转换为 TreeNode 结点
            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);
    }
}

treeify():双向链表转为红黑树

final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    for (TreeNode<K,V> x = this, next; x != null; x = next) {  // x/this:双向链表头节点
        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;  //dir:标识大小(负数插入左子树中,正数插入右子树中);ph:父节点hash值;pk:父节点key
                K pk = p.key;
                if ((ph = p.hash) > h)  //要插入的节点hash值比父节点小(往左继续迭代)
                    dir = -1;
                else if (ph < h)  //要插入的节点hash值比父节点大(往右继续迭代)
                    dir = 1;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||  // hash相同但key不相同,且没有实现Comparable接口
                         (dir = compareComparables(kc, k, pk)) == 0)  // 或者key实现了Comparable接口,调用compareTo()方法返回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);  //root节点放入数组中,且root节点是双向链表头节点
}
comparableClassFor():是否实现了 Comparable 接口

是否实现了 Comparable 接口,如果实现了则返回对应的 Class 对象,没有实现则返回 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) // String类型直接返回
            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) &&  // 如果实现了Comparable接口就继续往下走
                    (as = p.getActualTypeArguments()) != null &&  //获取Comparable<>中的泛型数组
                    as.length == 1 && as[0] == c) // 泛型长度是1且类型是c,则返回
                    return c;
            }
        }
    }
    return null;  // 没有实现Comparable则返回Null
}

ParameterizedType 和使用:

compareComparables():调用Comparable接口的compareTo()方法
static int compareComparables(Class<?> kc, Object k, Object x) {
    return (x == null || x.getClass() != kc ? 0 :
            ((Comparable)k).compareTo(x));
}
tieBreakOrder():当key的比较结果相同时,打破平衡(使比较结果不相等)
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) ?  // 类名也相同的情况比较地址hash
             -1 : 1);
    return d;
}

5. split():分割红黑树

/**
 * 将红黑树中的节点拆分为低位和高位的双向链表,如果红黑树里结点数太少,则退化为单向链表。
 */
final void split(java.util.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
    // 低位双向链表头和尾
    TreeNode<K,V> loHead = null, loTail = null;
    // 高位双向链表头和尾
    TreeNode<K,V> hiHead = null, hiTail = null;
    // lc(low count):低位结点数量 hc(high count):高位结点数量
    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)
            // 如果低位链表结点数量少于6,则红黑树退化为单向链表
            tab[index] = loHead.untreeify(map);
        else {
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                // 双向链表转换为红黑树
                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);
        }
    }
}

untreeify():双向链表转单向链表

final Node<K,V> untreeify(java.util.HashMap<K,V> map) {
    Node<K,V> hd = null, tl = null;
    // q 是数组中的红黑树结点
    for (Node<K,V> q = this; q != null; q = q.next) {
    	// 将 TreeNode 对象替换为 Node 对象(红黑树结点转换为单向链表结点)
        Node<K,V> p = map.replacementNode(q, null);
        if (tl == null)
            hd = p;
        else
            tl.next = p;
        tl = p;
    }
    return hd;
}

6. putTreeVal():往红黑树中插入节点

与 treeify() 方法相比:

  • treeify():

    • 对红黑树根节点的判断
  • putTreeVal():

    • 多了 key 值相同时的判断

红黑树插入后平衡调整规则:

规定新插入的节点是红色:

  1. 根节点直接插入,变为黑色
  2. 如果父节点是黑色,则直接插入
  3. 如果父节点是红色:
    1. 叔叔节点是空或黑色,旋转加变色
    2. 叔叔节点是红色,父节点和叔叔节点变黑色,祖父节点变红色

在这里插入图片描述

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) { //this:TreeNode;h:要插入的key的hash值;k:要插入的key;v:要插入的value
    Class<?> kc = null;
    boolean searched = false;
    TreeNode<K,V> root = (parent != null) ? root() : this; //找到root节点
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk;  //dir:正负标识大小;ph:父节点hash值;pk:父节点key
        if ((ph = p.hash) > h)  //要插入的节点hash值比父节点小(往左继续迭代)
            dir = -1;
        else if (ph < h)  //要插入的节点hash值比父节点大(往右继续迭代)
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))  //key值相同
            return p;
        else if ((kc == null &&
                  (kc = comparableClassFor(k)) == null) ||  // hash相同但key不相同,且没有实现Comparable接口
                 (dir = compareComparables(kc, k, pk)) == 0) {  // 或者key实现了Comparable接口,调用compareTo()方法返回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,就返回
            }
            dir = tieBreakOrder(k, pk);  //打破要插入节点和父节点相等的状态(类名排序,类名相同时identityHashCode排序)
        }

        TreeNode<K,V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) { //<=0时走左边,>0时走右边
        	// 当遍历到叶子节点时
            Node<K,V> xpn = xp.next;
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);  //创建节点,next节点指向xpn(双向链表角度看,新节点插到了父节点后面)
            if (dir <= 0)
                xp.left = x;  //叶子节点赋值
            else
                xp.right = x;
            xp.next = x;  //父节点next指针指向新节点
            x.parent = x.prev = xp;  //新节点的parent和prev指针指向父节点
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;  // 双向链表指针调整
            moveRootToFront(tab, balanceInsertion(root, x));  // 平衡红黑树后,并root节点放入数组中,且root节点是双向链表头节点
            return null;
        }
    }
}

find():在红黑树中查找

final TreeNode<K,V> find(int h, Object k, Class<?> kc) {  // h:被查找key的hash;k:被查找的key;kc:被查找的key的Class对象
    TreeNode<K,V> p = this;  // 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)))  // 找到了对应的Key
            return p;  //将找到的Key返回
        // 下面都是hash冲突的情况
        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)  //key实现了Comparable接口的情况
            p = (dir < 0) ? pl : pr;
        else if ((q = pr.find(h, k, kc)) != null)  //比较不出大小来,先右子树继续遍历
            return q;
        else  //最后左子树遍历
            p = pl;
    } while (p != null);
    return null;  // 没有查到返回null
}

balanceInsertion():(红黑树中插入值后)平衡红黑树

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,  // 红黑树根节点
                                            TreeNode<K,V> x) {  // 新插入的节点
    x.red = true;  //新插入的节点是红色的
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //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)) {  //xp 在 xpp 左子树的情况(如下图)
            if ((xppr = xpp.right) != null && xppr.red) {  // 如图1,叔叔(右)节点是红色的
                xppr.red = false;  //叔叔节点和父节点变黑
                xp.red = false;
                xpp.red = true;   //祖父节点变红,如图2
                x = xpp;  //继续迭代
            }
            else {   // 图3
                if (x == xp.right) {
                    root = rotateLeft(root, x = xp);  //左旋 图4
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;  // 图5
                        root = rotateRight(root, xpp);  //右旋,如图6
                    }
                }
            }
        }
        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);  // 再左旋
                    }
                }
            }
        }
    }
}

在这里插入图片描述

rotateLeft():左旋

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) { 
        if ((rl = p.right = r.left) != null)  // 图2
            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;  // 图3
        r.left = p;	 // 图4
        p.parent = r;  
    }
    return root;
}

在这里插入图片描述

rotateRight():右旋

与左旋操作相反

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

moveRootToFront():将 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) {
        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;  // 数组中放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;
            // root节点放到first节点前面(双向链表头节点)
            if (first != null)
                first.prev = root;
            root.next = first;
            root.prev = null;
        }
        assert checkInvariants(root);
    }
}

总结

  1. 容量是2的幂次,当存的数达到 容量*0.75 时,扩容
  2. 数组先存单向链表,链表上的节点个数超过8时,如果数组大小没有达到64,则扩容,否则链表转换成双向链表(仍然存在)再转换成红黑树
  3. 扩容时,如果红黑树中的节点个数小于等于6,则红黑树退化成单向链表
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JDK8 HashMap put() 方法源码分析 的相关文章

随机推荐

  • 深度讲解一下远程控制软件哪家好?推荐一款免费不限速的好软件给大家!

    小编今天要推荐一款较为小众的远程控制软件 通过远程桌面可以极大地方便我们进行远程技术支持 远程办公 然而我们熟知 QQ 远程 windows自带的远程协助 使用起来并不理想 不是连接不顺畅就是操作技术高 相比之下 专门的远程桌面软件的体验更
  • NumPy 学习笔记(二):NDArray

    导入 NumPy 开始学习 import numpy as np 不用 Python 非好汉 不晓 NumPy 真遗憾 本专栏 将使用 图解 以及 脑图 的方法来记录我的 图解 NumPy 学习笔记 NumPy 是 Numerical Py
  • 悟空crm-0.5.4 (OpenLogic CentOS7.2)

    平台 CentOS 类型 虚拟机镜像 软件包 5kcrm0 5 4 centos7 2 lamp stack 5 6 22 commercial crm lamp 服务优惠价 按服务商许可协议 云服务器费用 查看费用 立即部署 产品详情 产
  • ValueError: not enough values to unpack (expected 2, got 1)错误解决方案

    在学习python时 遇到了错误 现已解决 源代码如下 role line spoken each line split 1 错误如下 ValueError not enough values to unpack expected 2 go
  • 搜索服务应用:solr的使用

    开始前 环境 solr4 10 3 jdk1 7 tomcat7 下载地址 http archive apache org dist lucene solr 说明 solr和lucen更新是同步的 请配对使用 lucene用什么版本solr
  • 金山文档手机app服务器异常,手机金山文档出现这个文件大家有没有遇到过,在线求解谢谢了。{...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 手机金山文档出现这个文件大家有没有遇到过 在线求解谢谢了 version 3 UpdateFrequency 1 AppIDConfig Global DataReport UserPortra
  • 相机参数原理深入剖析 与 实际运用

    1 相机内参与应用 fx fy u0 v0只与摄像机内部参数有关 故称矩阵M1为内参数矩阵 其中fx f dX fy f dY 分别称为u轴和v轴上的归一化焦距 f是相机的焦距 dX和dY分别表示传感器u轴和v轴上单位像素的尺寸大小 单位为
  • 三角函数公式

    转自 https baike baidu com item E4 B8 89 E8 A7 92 E5 87 BD E6 95 B0 E5 85 AC E5 BC 8F 4374733 fr aladdin 三角函数是数学中属于 初等函数中的
  • 现在学java的都是傻子

    不经意的看见 看到学java的都是傻子 当不经意看到 说明 这个最近已经在网上疯传了很多 说明目前这个行业真的已经不好了 所以你得自己当心了 在这个行业不知有多少学习了又放弃了 博主我也是其中之一 从博主我的名字相信大家也可以看出来 从放弃
  • Stem-and-Leaf Plot in R

    Data set faithful 272 2 Waiting time between eruptions and the duration of the eruption for the Old Faithful geyser gt d
  • flink源码阅读---Flink intervalJoin 使用和原理分析

    1 前言 Flink中基于DataStream的join 只能实现在同一个窗口的两个数据流进行join 但是在实际中常常会存在数据乱序或者延时的情况 导致两个流的数据进度不一致 就会出现数据跨窗口的情况 那么数据就无法在同一个窗口内join
  • 主成分分析PCA以及特征值和特征向量的意义

    定义 主成分分析 Principal Component Analysis PCA 是一种统计方法 通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量 转换后的这组变量叫主成分 PCA的思想是将n维特征映射到k维上 k
  • android TextUtils工具类的用法

    方法摘要 static CharSequence 返回一个字符序列 commaEllipsize CharSequence text TextPaint p float avail String oneMore String more Co
  • Windows小技巧7--Sublime Text 3使用总结

    Windows小技巧7 Sublime Text 3使用总结 Sublime Text 是一个代码编辑器 也是HTML和散文先进的文本编辑器 Sublime Text是由程序员Jon Skinner于2008年1月份所开发出来 它最初被设计
  • C++中函数返回引用,及问题

    目录 函数返回值 返回引用 C 基础知识 函数返回引用深度解析 关于函数调用返回引用错误并且每次调用不一致的分析与解决 将引用作为函数返回值的格式 好处和规则 实用经验 45 禁止函数返回局部变量的引用 1 返回的是一个引用类型 也就是返回
  • 整数溢出的漏洞危害和预防

    智能合约作为区块链2 0的代表技术 适应于区块链去中心化 分布式的特点 具有独立运行 不可篡改的优良特性 可用于实现包含金融工具在内的各类分布式应用 开发者可以自行定义交易逻辑并开发代码发布到链上 合约代码在矿工节点的虚拟机环境 如EVM
  • vue踩坑填坑(一):引入模块组件

    在webpack vue开发中 如果在一个vue文件中引入另外一个封装的模块组件的vue文件 则有以下两种方式 首先想要在以下代码中引入一个封装好的输入框组件input text vue
  • cf服务器维护会不会掉分,《cf》枪王排位长时间不打会不会掉分? 枪王排位扣分机制介绍...

    川北在线核心提示 原标题 cf 枪王排位长时间不打会不会掉分 枪王排位扣分机制介绍 CF枪王排位大师以上不打会掉分么 很多小伙伴都在问枪王排位长时间不打会不会掉分 为此牛游戏小编为大家带来cf枪王排位扣分机制介绍 一起来看看枪王排位长时间不
  • Basic Level 1014 福尔摩斯的约会 (20分)

    题目 大侦探福尔摩斯接到一张奇怪的字条 我们约会吧 3485djDkxh4hhGE 2984akDfkkkkggEdsb s hgsfdk d Hyscvnm 大侦探很快就明白了 字条上奇怪的乱码实际上就是约会的时间星期四 14 04 因为
  • JDK8 HashMap put() 方法源码分析

    文章目录 一 前置知识 红黑树定义 二 构造方法 HashMap HashMap int initialCapacity float loadFactor tableSizeFor int cap 计算hashmap初始容量 三 put 方