前言

集合在Java中是非常重要,不仅在Java项目开发中高频使用,在面试中也经常出现集合相关的问题。本文主要给大家介绍一下ArrayList集合类。

一、ArrayList简介

ArrayList是实现了基于动态数组的数据结构,一个ArrayList集合是存储在一块连续的内存中。如图1.1。

1、定义一个ArrayList集合A,并插入对象(Obj01-Obj05)。

2、A.add(Obj06)会将Obj06添加到集合A的最后面。

3、A.add(3,Obj07)是将Obj07插曲到集合A第三个元素(Obj04)的前面,这时候就需要将Obj04、Obj05、Obj06分别向下移动一个位置供Obj07插入。

4、A.remove(Obj02)将Obj02删除,后面的对面都需要像前移一个位置。

从步骤3和4中可以看出,ArrayList在插入和删除元素的时候,必然导致在该位置后的所有元素需要像后或者向前移动,因此,其效率相对会比较低。但是ArrayList的查询效率很高,因为只要知道ArrayList在内存中的开始位置,通过计算就能准确的定位到某一个元素的位置。

二、ArrayList 源码解析

ArrayList类属性

//ArrayList默认容量为10
private static final int DEFAULT_CAPACITY = 10;
//空数组,当传入的容量为0的时候使用,通过new ArrayList(0)创建时用的是这个空数组。
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
//空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
//存储元素的数组,真正存放元素的地方,使用transient是为了不序列化这个字段。
transient Object[] elementData;
//ArrayList中真正存储元素的个数
private int size;
//ArrayList容量的最大值
private static final int MAX_ARRAY_SIZE = 2147483639;

构造方法ArrayList(int var1)

ArrayList(int var1)方法是创建一个初始容量为var1的集合,var1是创建集合传入的初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。如new ArrayList(3),会创建一个初始容量为3的集合,那么初始容量为3是不是这个集合就只能存储3个元素呢?答案是否定的,初始化容量为3的集合是可以存储超过3个元素的,因为ArrayList可自动扩容。

public ArrayList(int var1) {
 if (var1 > 0) {
 // 如果传入的初始容量大于0,就新建一个数组存储元素
 this.elementData = new Object[var1];
 } else {
 if (var1 != 0) {
 //如果不大于0且不等于0,则抛出异常(因初始化容量不能小与0)
 throw new IllegalArgumentException("Illegal Capacity: " + var1);
 }
 //如果初始化容量等于0,则使用空数组 EMPTY_ELEMENTDATA
 this.elementData = EMPTY_ELEMENTDATA;
 }

 }

构造方法ArrayList()

该构造方法没有传入初始的容量,则默认使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这种方法是我们比较常用的。

public ArrayList() {
 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

构造方法ArrayList(Collection<? extends E> var1)

该方法是把初始化的时候把传入的集合的元素初始化到ArrayList中,如果传入的集合元素个数为0,则初始化EMPTY_ELEMENTDATA空数组。

public ArrayList(Collection<? extends E> var1) {
 //将传入的集合转换成数据,并赋值给elementData
 this.elementData = var1.toArray();
 if ((this.size = this.elementData.length) != 0) {
 // 检查var1.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型
 if (this.elementData.getClass() != Object[].class) {
 this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
 }
 } else {
 //如果传入的集合元素个数为0,则初始化EMPTY_ELEMENTDATA空数组。
 this.elementData = EMPTY_ELEMENTDATA;
 }

 }

add(E var1)方法

在集合末尾添加一个新元素。

public boolean add(E var1) {
 //判断如果新增一个元素,会不会超过ArrayList的容量,若超过,则扩容
 this.ensureCapacityInternal(this.size + 1);
 //将新的元素添加到集合的末尾
 this.elementData[this.size++] = var1;
 return true;
 }

ensureCapacityInternal(int var1)方法

该方法用来判断var1有没有超过当前ArrayList容量的大小,如果超过了则自动扩容,从这个方法我们可以看到,当使用ArrayList()构造方法的时候,初始化为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,但是当第一次使用Add方法往集合中添加元素的时候,集合会扩容为10。

private void ensureCapacityInternal(int var1) {
 //如果是空数组,则取var1和10两个值中的最大值
 if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 var1 = Math.max(10, var1);
 }
 //将上一步取到的最大值,扩容ArrayList的大小
 this.ensureExplicitCapacity(var1);
 }

ensureExplicitCapacity(int var1)方法

该方法判断var1与集合现在的元素个数的差,如果差值大于0,则表示现有集合的容量已经满足不了新增的元素,就会调用grow方法扩容。

private void ensureExplicitCapacity(int var1) {
 ++this.modCount;
 if (var1 - this.elementData.length > 0) {
 this.grow(var1);
 }
 }

grow(int var1)方法

private void grow(int var1) {
 //获取ArrayList现有的容量
 int var2 = this.elementData.length;
 //将现有容量的值乘1.5倍(扩容以后的值)
 int var3 = var2 + (var2 >> 1);
 //判断扩容后的容量是否能满足需要的容量,如果不能则已需要的容量为准
 if (var3 - var1 < 0) {
 var3 = var1;
 }
 // 如果新容量已经超过最大容量了,则使用最大容量
 if (var3 - 2147483639 > 0) {
 var3 = hugeCapacity(var1);
 }
 // 以新容量拷贝出来一个新数组
 this.elementData = Arrays.copyOf(this.elementData, var3);
 }

add(int n, E var2)方法

该方法是将元素var2插入到集合的第n个位置。

public void add(int var1, E var2) {
 //检查要插入的位置是否合法(插入的位置不能小于0且不能大于当前集合的容量)
 this.rangeCheckForAdd(var1);
 判断如果新增一个元素,会不会超过ArrayList的容量,若超过,则扩容
 this.ensureCapacityInternal(this.size + 1);
 //将要插入位置的之后的所以元素往后移动一个位置
 System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
 //将新的元素插入对应的位置
 this.elementData[var1] = var2;
 ++this.size;
 }

remove(int var1)方法

删除var1位置的元素,集合中元素的位置是从0开始计算的,所以如果想删除第一个元素需用 remove(0)。

public E remove(int var1) {
 //检查要移除的元素位置是否合法(var1不能小于0且不能大于集合容量的最大值)
 this.rangeCheck(var1);
 ++this.modCount;
 //获取要删除的元素
 Object var2 = this.elementData(var1);
 int var3 = this.size - var1 - 1;
 if (var3 > 0) {
 //将要删除元素之后的所有元素往前移动一个位置
 System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3);
 }
 //将最后的元素设置为null并将size减1
 this.elementData[--this.size] = null;
 //返回删除的元素
 return var2;
 }

remove(Object var1)

根据元素删除,从该方法的源码中,我们可以看到,如果集合中存在多个var1元素,remove方法只会按顺序找到第一个var1元素并删除,并不会把所有的var1元素都删除。

public boolean remove(Object var1) {
 int var2;
 //当var1为null时,循环遍历集合中的元素,找到一个null的元素并删除,返回true
 if (var1 == null) {
 for (var2 = 0; var2 < this.size; ++var2) {
 if (this.elementData[var2] == null) {
 this.fastRemove(var2);
 return true;
 }
 }
 } else {
 //当var1不为null时,循环遍历集合中的元素,找到一个var1的元素并删除,返回true
 for (var2 = 0; var2 < this.size; ++var2) {
 if (var1.equals(this.elementData[var2])) {
 this.fastRemove(var2);
 return true;
 }
 }
 }

 return false;
 }

fastRemove(int var1)方法

删除第var1个位置的元素。

private void fastRemove(int var1) {
 ++this.modCount;
 //如果var1不是最后一位,则将var1之后的元素依次往前移动一位
 int var2 = this.size - var1 - 1;
 if (var2 > 0) {
 System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
 }
 //将集合中最后的元素设置为null
 this.elementData[--this.size] = null;
 }

System.arraycopy()

System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。arraycopy的参数如下:

arraycopy(Object src,int srcPos,Object dest,int destPos,int length);
src表示源数组
srcPos表示源数组要复制的起始位置
desc表示目标数组
destPos表示目标数据要复制的其实位置
length表示要复制的长度