Java集合精选常见面试题( 四 )

minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法 。

  • 当 add 第 2 个元素时 , minCapacity 为 2,此时 e lementData.length(容量)在添加第一个元素后扩容成 10 了 。此时,minCapacity - elementData.length > 0 不成立 , 所以不会进入 (执行)grow(minCapacity) 方法 。
  • 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10 。
  • 直到添加第 11 个元素 , minCapacity(为 11)比 elementData.length(为 10)要大 。进入 grow 方法进行扩容 。
    grow()
    /*** 要分配的最大数组大小*/private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** ArrayList扩容的核心方法 。*/private void grow(int minCapacity) {// oldCapacity为旧容量,newCapacity为新容量int oldCapacity = elementData.length;//将oldCapacity 右移一位,其效果相当于oldCapacity /2,//我们知道位运算的速度远远快于整除运算 , 整句运算式的结果就是将新容量更新为旧容量的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,//如果minCapacity大于最大容量 , 则新容量则为`Integer.MAX_VALUE`,否则 , 新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8` 。if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = https://www.huyubaike.com/biancheng/Arrays.copyOf(elementData,newCapacity);}int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍 , 否则是 1.5 倍左右)! 奇偶不同,比如 :10+10/2 = 15,33+33/2=49 。如果是奇数的话会丢掉小数.
    >(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方 。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2 。对于大数据的 2 进制运算 , 位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
    我们再来通过例子探究一下grow() 方法 :
    • 当 add 第 1 个元素时,oldCapacity 为 0 , 经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10) 。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法 。数组容量为 10,add 方法中 return true,size 增为 1 。
    • 当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立 。新容量没有大于数组最大 size , 不会进入 hugeCapacity 方法 。数组容量扩为 15,add 方法中 return true,size 增为 11 。
    • 当第16个元素进入容器时再次扩容
    这里补充一点比较重要,但是容易被忽视掉的知识点:
    • java 中的 length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
    • java 中的 length() 方法是针对字符串说,如果想看这个字符串的长度则用到 length() 这个方法.
    • java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
    hugeCapacity()
    从上面 grow() 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE , 如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8
    private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();//对minCapacity和MAX_ARRAY_SIZE进行比较//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小//若MAX_ARRAY_SIZE大 , 将MAX_ARRAY_SIZE作为新数组的大小//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}System.arraycopy()Arrays.copyOf()
    阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法 。比如:我们上面讲的扩容操作以及

    推荐阅读