HashMap源码

发布时间 2023-08-01 12:40:10作者: Bright丶

put方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;  // 这个p是开始定位到的Node或者TreeNode
    if ((tab = table) == null || (n = tab.length) == 0)   // 如果数组是null或者长度是0
        n = (tab = resize()).length;  // 先扩容,然后获取数组长度
    if ((p = tab[i = (n - 1) & hash]) == null)  // 获取要存储位置的第一个元素是null,说明该位置是空的
        tab[i] = newNode(hash, key, value, null);  // 创建一个新的节点对象放在该位置  【添加元素位置1】
    else {  // hash冲突,位置上有数据了
        Node<K,V> e; K k;  // 这个e就是最终找到的节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))  // 第一个的hash值和要存的数据hash相同,而且key相同
            e = p;  // 说明要找的就是这个元素
        else if (p instanceof TreeNode)  // 如果第一个节点类型是TreeNode,说明已经变成红黑树了
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  // 执行红黑树的查找流程
        else {  // 不是红黑树,第一个也不是,就要按照链表继续往下找了
            for (int binCount = 0; ; ++binCount) {  // 循环遍历链表
                if ((e = p.next) == null) { // 下一个节点是null,也就是之前没有存过这个数据,后面当前没有其他节点了
                    p.next = newNode(hash, key, value, null);  // 创建一个新元素放到链表中下一个节点位置就行了    【添加元素位置2】
                    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) // 允许覆盖或者值是null
                e.value = value;  // 设置新值
            afterNodeAccess(e);  // 后置处理,空方法
            return oldValue;  // 返回旧值
        }
    }
    // 如果是新添加的元素会执行到这里,上面的【添加元素位置1】【添加元素位置2】
    ++modCount;
    if (++size > threshold)  // 判断容量是否超过阈值
        resize();  // 扩容
    afterNodeInsertion(evict);  // 后置处理,空方法
    return null;  // 返回null
}

1、数组是不是空,是空的话先扩容;

2、根据hash定位到数组的索引位置,如果是null,直接创建节点添加到该位置;

3、如果不是null,类型是TreeNode,说明已经变成红黑树,按照红黑树的方式进行查找;

4、如果是普通Node类型,就按照链表进行查找;

扩容resize方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;  // 旧容量,如果没有初始化就是0,有数据就是数组长度
    int oldThr = threshold;  // 扩容阈值
    int newCap, newThr = 0;
    if (oldCap > 0) {  // 旧容量 > 0
        if (oldCap >= MAXIMUM_CAPACITY) { // 旧容量大于最大值
            threshold = Integer.MAX_VALUE; // 阈值更新为最大
            return oldTab; // 不能扩容了,直接返回旧数组
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)  // 新容量为旧容量的2倍 小于最大容量 而且 旧容量达到初始化阈值
            newThr = oldThr << 1; // double threshold  // 新容量为旧容量的2倍
    }
    else if (oldThr > 0)  // 旧容量为0,旧阈值大于0
        newCap = oldThr;  // 新容量为旧阈值
    else {                // new HashMap()第一次put会执行这里
        newCap = DEFAULT_INITIAL_CAPACITY;  // 默认初始化容量 16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  // 默认初始化阈值 12
    }
    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;  // 把原来位置设为null
                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;
}

红黑树插入

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)  // 要插入的hash值比根节点小
            dir = -1;
        else if (ph < h) // 要插入的hash值比根节点大
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))  // 要插入的hash值等于根节点,而且key相同,说明就是根节点
            return p;
        else if ((kc == null &&  // 第一次默认是null,后面是String
                  (kc = comparableClassFor(k)) == null) ||  // k的类型是不是实现了Comparable接口,k一般都是String
                 (dir = compareComparables(kc, k, pk)) == 0) {// 1、判断pk == null,也就是root节点的key是不是null,一般不会存null,不符合	            												// 2、判断pk的类型是不是kc,也就是root节点的key类型是不是String,一般都符合
            											      // 3、符合2的话,比较k.compareTo(pk),前面的if判断已经知道两个key是不相等的
            											      // 也就是发生了hash冲突,所以这里一定不是0,不太懂这里的逻辑
            if (!searched) {  // 初始值为false
                TreeNode<K,V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&  // 左子树不为null
                     (q = ch.find(h, k, kc)) != null) ||  // 左子树找到了
                    ((ch = p.right) != null &&  // 右子树不为null
                     (q = ch.find(h, k, kc)) != null))  // 右子树找到了
                    return q;  // 找到了就返回
            }
            dir = tieBreakOrder(k, pk); // hash冲突也要有个顺序,相等放在左边,dir=-1
        }

        TreeNode<K,V> xp = p;  // 根节点
        if ((p = (dir <= 0) ? p.left : p.right) == null) {  // dir小于等于0表示比根节点小,放在左子树,dir>0表示比根节点大,放在右子树
            												// 子树遍历到头了,那就插入新节点,否则重新循环
            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;  // 记录树节点的parent和链表的prev
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;  // 记录链表关系
            moveRootToFront(tab, balanceInsertion(root, x));  // 平衡操作,旋转
            return null;  // 插入新节点,返回null
        }
    }
}

tableSizeFor方法

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

如果指定了初始容量,就按照这个方法计算出不小于指定容量的最小的2的幂作为初始容量。

基本思路:

先把指定的cap减个1,计算完再加个1,目的是为了防止cap本身就是2的幂,最后算出来的变成了cap的2倍,比如指定的是4,最后计算出来的是8;

假设初始值为0001xxxxxxxxxxx,x表示不确定0还是1

右移1位按位取或操作,得到结果00011xxxxxxxxxxxx,最左侧有效位变为两个1

右移2位按位取或操作,得到结果0001111xxxxxxxxxxx,最左侧有效位变为4个1

依次类推,右移4位变为8个1,右移8位变为16个1,右移16位变为32个1

因为是int类型的,所以最长32位,最多移动16位就把有效位全部变为1了,最后再加1,就变成2的n次幂了