菜鸡啄源码——HashMap之扩容resize()详解,超详细修炼手册。

1. 扩容第一步: 确定新数组的 容量 和 阈值(极限值/临界值)


 #### 1. 扩容第一步: 确定新数组的 容量 和 阈值(极限值/临界值)

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//拿到oldTab,在第2步操作时候会用到,在此可以带过。
        //初次put()的时候旧容量=0,其余情况旧容量为数组长度。
        //此处亦说明了hashmap初始化的时候容量其实是0,只有当第一次进行put操作时,才会真正的初始化。
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        /**
         *原阈值oldThr=threshold,threshold是int类型全局变量,默认初始值是0;
         *如果new操作调用的无参构造,即没有传入initialCapacity的时候,使用的threshold==0,反之则不为零;
         *而后每次扩容都调用了tableSizeFor(initialCapacity),然后再把返回值赋给threshold;
         *tableSizeFor(..)方法的作用是将传入的initialCapacity规范成2的n次方;
         *因而oldThr(原阈值)始终>=0;
         */
        int oldThr = threshold;
     	int newCap, newThr = 0; //这两个变量分别代表了  新容量 和 新阈值
        if (oldCap > 0) {//当旧容量>0,即非第一次put,第一次插入跳过这里
            if (oldCap >= MAXIMUM_CAPACITY) {//旧容量超过最大容量时,不超过则跳过这里
                /**将当前阈值threshold = Integer.MAX_VALUE 。
                 *(tip1 : Integer.MAX_VALUE = 0x7fffffff ,是 int 类型 能最大表示值)
                 *(tip2 : MAXIMUM_CAPACITY = 1<<30 ,即Integer.MAX_VALUE == MAXIMUM_CAPACITY*2 -1)
                 */ 
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //旧容量扩容一倍 也未超出 最大容量MAXIMUM_CAPACITY 的情况下
            //值得注意的是,无论判断通不通过,这里的newCap已经被赋值,2倍于旧容量 
           == else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY) ==
                newThr = oldThr << 1; // double threshold //判断通过,阈值也变为原来的2倍
        }
		//以下**else if** 和 **else** 对应了 第一次 put 操作 的**两种情况**:
        //(1)进入此代码块的先决条件是(oldCap == 0 & 原阈值oldThr > 0 )
        //即在创建hashmap对象时候使用的是有参构造。(构造可见附录)
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //(2)创建hashmap对象时调用的是无参构造,使用默认的两个变量作为初始化参数
        else {               // zero initial threshold signifies using defaults
            //默认初始化容量DEFAULT_INITIAL_CAPACITY==16(第一次put后的数组长度)
            newCap = DEFAULT_INITIAL_CAPACITY;
            //DEFAULT_LOAD_FACTOR 是 源码默认的负载因子,是一个浮点型变量,值为0.75f
            //第一次put之后的阈值= (int)(0.75f*16),即为 12 
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //进入此代码块的先决条件是 ,程序运行通过了 上一个if判断里 **嵌套的if判断**
        //即 旧容量 >= 最大容量 的情况。
        if (newThr == 0) { 
        	//注意:此处的newCap已经是旧容量的2倍;
        	//而loadFactor又分为 **默认值** 和 **非默认值** **两种情况**:
        	//(1)通过HashMap(int initialCapacity, float loadFactor)构造传入的是为非默认值情况,将使用传入的参数作为负载因子;
        	//(2)通过其余三个构造都是使用的默认值DEFAULT_LOAD_FACTOR == 0.75f
        	//比如: public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
            float ft = (float)newCap * loadFactor; 
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //如此,确定了各种情况下的新数组 新容量newCap , 新阈值newThr ;
        //将threshold = newThr 。
        threshold = newThr;
        
        
	↑ 上下代码相连 ↓

2. 扩容第二步:将旧数组内容 复制到 新数组 当中(扩容的本质是 新数组替代旧数组)

 ####2. 将旧数组内容 复制到 新数组 当中(扩容的本质是 新数组替代旧数组)
		
		//根据上面的newCap创建一个数组对象,准备复制操作
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;//将table的引用指向新数组对象
        if (oldTab != null) {//如果oldTab是非空的,即有进行put操作而引发过初始化的
            for (int j = 0; j < oldCap; ++j) {//遍历数组
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {//当前元素不为空(如果是空,没有必要复制到新数组)
                    oldTab[j] = null;//在赋值给e之后,此数组元素设为null
                    if (e.next == null)
                    	//如果只是数组部分有元素,即元素的next不指向一个对象,是为null
                    	//利用 hash算法的返回值 和 (新容量newCap - 1) 进行与运算(&);
                    	//结果作为目标元素在新数组中的索引,用以确定重排序的存储位置
                    	//tip1: hash算法(调用hashCode()结果值的高16位和低16位进行异或^运算后 返回结果)
                    	//tip2: 0 ~ newCap-1 共newCap 长度;
                    	//		**保证容量newCap为2的n次方,可以保证数据的散列性!!**
                    	//		因为当newCap为2的n次方时,其值减1一定是01......1(首位0+全1)
                    	//		 当newCap不为2的n次方时,其值减1一定不是01......1,而是0、1掺杂
                    	//比如:15==>01111 ,减1 = 01110 ,与hash算法的返回值与运算结果,
                    	//无论hash算法的返回值最后一位是0还是1,与运算之后都是0,这将增加hash碰撞的可能性。
                    	//为了降低碰撞的可能性,特意将newCap向2的n次方看齐,使其减1后有最多的1,
                    	//	再和hash返回值进行&运算,保证散列性。
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)//若果当前元素时红黑树对象时
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//调用split()方法拆分重排
                    else { // preserve order//若果当前对象是链表中的一员
                    	//loHead,loTail分别代表 **不需要移动** 的 **链头** 和 **链尾**
                        Node<K,V> loHead = null, loTail = null;
                        //hiHead ,hiTail 分别代表 **需要移动** 的 **链头** 和 **链尾**
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        
                        //循环确定原链表中每一个元素的去向,分两种情况:
                        //(1)要么是新索引 = 原索引 ;
                        //(2)要么是新索引 = 原索引 + 旧容量 ;
                        //因为newCap-1比oldCap-1只是多了一位1,然后通过和hash算法返回值与运算
                        //返回索引值,将当前元素存入对应的下标(对应的链表中)。
                        //再次与hash返回值与运算的时候,决定元素去留的只是最高位与运算的结果。
                        do {
                            next = e.next;
                            
                            //e.hash & oldCap ===> e.hash & 10....0
                            //最高位决定去留(即最高位的1和hash返回值 与运算的结果作为对象在新数组的索引)
                            //(“去留”在这里 表示 在新数组中是否保持和旧数组相同的索引(或理解为下标) 
							//hash和最高位的与运算结果若为0,说明在新数组保持和当前一样的索引
                            if ((e.hash & oldCap) == 0) {//jdk1.8 :尾插法
                                if (loTail == null)//链尾为空==>首次插入,将链尾指向e
                                    loHead = e;
                                else
                                    //链尾有元素,链尾的next指向e,当有多个元素时候,while()完成链表的追加。
                                    loTail.next = e;
                                loTail = e;	//使得链尾指向最新插入的元素/对象
                            }
                            else {//与运算结果为1,说明当前元素在新数组的索引要发生改变了。
                                if (hiTail == null)//链尾为空==>是首次插入
                                    hiHead = e;		//链头指向当前元素(区别于之前链头)
                                else
                                //链尾有元素,链尾的next指向e,当有多个元素时候,while()完成链表的追加。
                                    hiTail.next = e;
                                hiTail = e;//新插入的元素作为链尾(jdk1.8:尾插法)
                            }
                        } while ((e = next) != null);
                        //将不需要移动的链表拼接到与旧数组相同的索引(下标)位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //将需要移动的链表拼接到新索引(旧索引+旧容量oldCap)位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;//返回扩容后的对象
    }

附录
(1)int hash(Object key)

·key != null 时 ,调用hashCode()方法将返回值高低16位异或^运算后返回

    static final int hash(Object key) {
        int h;
        //1.key == null, 返回null
        //2.key != null, 调用hashCode()返回一个int类型32位数值,
        //	让结果的高16位和低16位进行异或^运算后返回。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
(2)int hashCode() 本地(native)方法

·返回一个32位的int类型结果值,因为是一个算法,相同的对象一定有相同的hashCode值

    public native int hashCode();
(3)HashMap四个构造方法
①HashMap(int initialCapacity, float loadFactor)
    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);
    }
②HashMap(int initialCapacity)
    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

③HashMap()
    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

④HashMap(Map<? extends K, ? extends V> m)
    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值