前言
集合在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表示要复制的长度