面试系列 | ArrayList

ArrayList有三种方式来初始化,构造方法源码如下:

ArrayList的构造方法

 

以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10

 

关于ArrayList的扩容机制

这里以无参构造函数创建的 ArrayList 为例分析

1. add() 方法

 

2. 再来看看 ensureCapacityInternal() 方法

可以看到 add 方法 首先调用了ensureCapacityInternal(size + 1)

 

当要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。

 

3. ensureExplicitCapacity() 方法

如果调用 ensureCapacityInternal() 方法就一定会进(执行)这个方法,下面我们来研究一下这个方法的源码!

 

我们来仔细分析一下:

 

当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时,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方法进行扩容。

 

4. grow() 方法

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后) JDk1.6版本时,扩容之后容量为 1.5 倍+1!详情请参考源码

 

我们再来通过例子探究一下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。

以此类推······

这里补充一点比较重要,但是容易被忽视掉的知识点:

java 中的 length 属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.

java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.

java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

 

5. hugeCapacity() 方法

从上面 grow() 方法源码我们知道:如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8。

 

阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及add(int index, E element)、toArray() 等方法中都用到了该方法!

System.arraycopy() 方法:

 

一个简单的方法测试如下:

public class ArraycopyTest {  public static void main(String[] args) {    // TODO Auto-generated method stub    int[] a = new int[10];    a[0] = 0;    a[1] = 1;    a[2] = 2;    a[3] = 3;    System.arraycopy(a, 2, a, 3, 3);    a[2]=99;    for (int i = 0; i < a.length; i++) {      System.out.println(a[i]);    }    // 结果为: 0 1 99 2 3 0 0 0 0 0   }}

 

Arrays.copyOf()方法:

 

使用 Arrays.copyOf()方法主要是为了给原有数组扩容,测试代码如下:

public class ArrayscopyOfTest {  public static void main(String[] args) {    int[] a = new int[3];    a[0] = 0;    a[1] = 1;    a[2] = 2;    int[] b = Arrays.copyOf(a, 10);    System.out.println("b.length=" + b.length);  }  // 结果为: b.length=10}

 

6. ensureCapacity() 方法

ArrayList 源码中有一个 ensureCapacity 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?

 

最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数

我们通过下面的代码实际测试以下这个方法的效果:

public class EnsureCapacityTest {  public static void main(String[] args) {    ArrayList<Object> list = new ArrayList<>();    final int N = 10000000;    long startTime = System.currentTimeMillis();    for (int i = 0; i < N; i++) {      list.add(i);    }    long endTime = System.currentTimeMillis();    System.out.println("使用ensureCapacity方法前:" + (endTime - startTime));    list = new ArrayList<>();    long startTime1 = System.currentTimeMillis();    list.ensureCapacity(N);    for (int i = 0; i < N; i++) {      list.add(i);    }    long endTime1 = System.currentTimeMillis();    System.out.println("使用ensureCapacity方法后:" + (endTime1 - startTime1));  }}// 运行结果:// 使用ensureCapacity方法前:2740// 使用ensureCapacity方法后:375

 

 

通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity 方法,以减少增量重新分配的次数

 

两者联系和区别

联系:

看两者源代码可以发现 copyOf() 内部实际调用了 System.arraycopy() 方法

区别:

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组。

 

来源于: JavaGuide

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值