了解HashMap扩容机制(JDK8)

前言

最近做项目的时候,添加了阿里的代码规范扫描插件
扫描的时候指出我的HashMap未指定初始化大小,这是不规范的
推荐:HashMap集合初始化时, 指定集合初始值大小。
网上给的解释是:
不指定的话,hashMap的每次put,会去判断是否到达HashMap的极限值(桶的大小*0.75(负载因子) ),到达即调用resize方法,如果频繁的调用resize会影响性能
注意:桶大小不等于size,可能桶的值已经32了,而size只有15,size表示HashMap中条目(即键-值对)的数量。
HashMap关键属性

    //默认的桶数组大小=16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    //最大size(超过这个值就将threshold修改为Integer.MAX_VALUE(此时桶大小已经是2的31次方了),表明不进行扩容了)
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //负载因子(后面很多地方会用到)
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    //无参构造方法
    public HashMap() { 
  	  This.loadFactor = DEFAULT_LOAD_FACTOR; //设置一个默认的负载因子,默认为0.75
    }
    Map<Object,Object> response=new HashMap<Object,Object>(int initialCapacity);

如果暂时无法确定hashMap的size,那么取默认值16
如果已知要存的大小,那么可以根据下面的式子求出预存值
式子: initialCapacity = (预备存储的大小 / 负载因子) +1

resize方法流程图

了解HashMap扩容机制(JDK8)_第1张图片
1.第一次调用put方法,会走

     Node<K,V>[] tab; Node<K,V> p; int n, i;
        //table就是桶数组,初始为null
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

2.然后调用resize()扩容

    else{ 
    	newCap = DEFAULT_INITIAL_CAPACITY; 
    	newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
     }

newCap的意思是:将用于hash的桶数组的长度,这里的默认值是16
newThr的意思是:当桶中的键值对的个数超过这个值时就进行扩容,这时候上面提到的负载因子就起作用了,所以这里的newThr为0.75×16 = 12

3.putVal()中判断hash桶是否需要扩容

    if (++size > threshold)
     		resize();

4.第一次之后put执行的code

   		 //肯定会进入这个分支
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }

//如果我们是这样调用:HashMap(7(任意满足条件的值)),那么经过第一次调用put()会初始化桶数组的大小和极限值一样,这里即为8。所以这里不会进入这个分支,但是newCap会变为16 判断条件处保证了无论是size 还是 threshold都是2的幂

       else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //所以会进入这里
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
        }
  	  threshold = newThr;//计算的出这里的threshold即阈值会由刚才的8变为12

5.为什么HashMap容量一定要为2的幂?
(涉及到结构)目的就是为了让HashMap的元素存放更均匀。最理想的状态是,每个Entry数组位置都只有一个位置,即next没有值,也就是没有单链表,这样查询效率高,不用遍历单链表,更不用去用equals比较K。一般考虑分布均匀,都会用到%(取模),哈希值%容量=bucketIndex。
6.是否会出现size无限扩容问题
答案是不会,cap最大为Integer.MAX_VALUE;

    //每次都加倍,所以最终肯定会加倍到MAXIMUM_CAPACITY,会进入这个分支。 
       if (oldCap >= MAXIMUM_CAPACITY) 
       {  threshold = Integer.MAX_VALUE;
        return oldTab; 
       }
       //这个size类型是int,那么最大就是Integer.MAX_VALUE,所以不会有++size > threshold的情况,往后都不会进行扩容了。 
       if (++size > threshold) 
       resize();

测试:Integer.MAX_VALUE++ 不会报错,但是也无法进入if代码块
在这里插入图片描述
核心代码解析

    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;//HashMap无参构造函数后table是Null
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;//默认构造器的情况下为0
            int newCap, newThr = 0;
            if (oldCap > 0) {//桶已扩容
                 //当前桶容量大于最大值得时候返回当前table
                 if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                //桶的容量乘以2,桶阈值也乘以2           
                newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
            //使用带有初始容量的构造器时,table容量为初始化得到的threshold
            newCap = oldThr;
            else {  //默认构造器下进行扩容  
                // zero initial threshold signifies using defaults
                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 = newTab;
          if (oldTab != null) {
            //对新扩容后的桶进行赋值,条件中的代码删减
            }
            return newTab;
        }

你可能感兴趣的:(Java)