HashMap-put过程源码分析

224 阅读3分钟
/**
int hash:根据key计算出来的hash值
k key:key
V value:value
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
         // 判断全局的table是否为空 
        if ((tab = table) == null || (n = tab.length) == 0)
            //  如果全局的table为空 就进入初始化容器 默认初始化容器为16 负载因子为0.75f
            n = (tab = resize()).length;
           // 根据(n-1)&hash值计算出数组的下标 如果定义的p=table[计算的下标]=null
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 就在此数组下标直接赋值
            tab[i] = newNode(hash, key, value, null);
            // 如果此数组下标中存在值 不为null 就走else判断逻辑 
            /**
            此时 Node<K,V> p 是有值的 
            */
        else {
            Node<K,V> e; K k;
            // 计算此时数组坐标上那个值的hash和key与新增进来的值和hash是否相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果相同 就将数组坐标的旧值 赋值给e
                // e是新插入进来的值 p是数组坐标上的值 因为hash计算出来的数组坐标是一样的
                // 现在的e就等于:之前的值p
                /**
                比如:之前的旧值为
                     p: Node<K,V,NEXT>=YAO,YAOXIANG,NULL
                      插入进来的新值为:
                     e: Node<K,V,NEXT>=YAO,YAOXIANG11,NULL
                      然后计算key相等 hash也相等 就直接
                      e=p;所以现在的e为旧值 现在就跳第三步
                **/
                e = p;
            // 如果key不相同 就走下面的判断逻辑
            // 现在的p为根据新增进来的key计算下标获取的值 
            /**
            instanceof:是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类创建的对象时,返回trur,否则返回false
            instanceof 的例子:
            public class Code {
             public static void main(String[] args) {
        Node<Integer> node = new Node<>(1, 2, new Node<>(1, 2, null, 2), 1);
        if( node instanceof Node){
            System.out.println(true);
        }
    }
    static class  Node<T>{
      private int key;
      private T value;
      private Node<T> next;
      private int hash;
      public Node(int key,T value,Node<T> next,int hash){
          this.key=key;
          this.value=value;
          this.next=next;
          this.hash=hash;
      }
    }
}

            
            */
            // 这里就是判断这里的Node是不是红黑树创建的 如果是就走里面的逻辑 否者跳下面
            else if (p instanceof TreeNode)
                 // 如果此时坐标上的p是红黑树节点 就走这里的逻辑 
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 如果此时坐标上的p不是红黑树是链表 就走这里的for循环逻辑 遍历链表
                for (int binCount = 0; ; ++binCount) {
                    // 这里就开始遍历链表 判断节点的next是否为null 如果为null就添加到尾节点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 当链表的长度大于等于7的时候  链表变为红黑树 (当红黑树一个阈值的时候 也会变为链表)                   链表变红黑树阈值: static final int TREEIFY_THRESHOLD = 8;
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 将整个table 和此hash值红黑树化
                            treeifyBin(tab, hash);
                        break;
                    }
                   // 遍历链表的时候 同时也要判断key是否相同 如果相同就覆盖 返回旧的value值 看第三步
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 第三步 e现在不为null e为旧值
            if (e != null) { // existing mapping for key
                // 记录旧值 返回
                /**
                所以当hash冲突且key相同时 hashMap.put() 会返回之前的旧值
                */
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    // 这里就是更新旧址  将插入进来的value 替换掉旧值的value
                    e.value = value;
                afterNodeAccess(e);
                // 返回旧址的value
                return oldValue;
            }
        }
        ++modCount;
       // 每当新增一个元素的时候 size++ 当size>阈值(负载因子0.75f*容器容量(默认16)=12)的时候就扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

思考 为什么扩容和初始化容器必须是2的次方幂?

思考 底层是如何保证你输入的初始化容器长度是2的次方幂?