前阵子面试有问到ArrayList的源码有没有了解,在这里记录一下我对它的理解。
说到ArrayList的话,首先了解一下它的数据结构:底层还是数组
//transient是为了防止elementData序列化,将其生命周期放在内存,不放至硬盘中
//elementData是用来存储数据的数组,也就是arraylist的底层数据结构
transient Object[] elementData;
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA用在没传入参数的时候
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//EMPTY_ELEMENTDATA 用来区别数组传参为0和没传参的情况
private static final Object[] EMPTY_ELEMENTDATA = {};
它和普通的数组有什么区别呢?为什么要推出这种ArrayList结构?
讲到这,可以了解一下基本数组,首先它是在初始化时需要指定数组长度,
存储的数据类型也只能是相同的一种如int/char等等,通过数组的下标索引可以快速访问某个位置上的数据。
而ArrayList与普通数组的区别就在于ArrayList是可以动态扩容的,
而且ArrayList继承于AbstratList类,
实现了RandomAccess, Cloneable, java.io.Serializable等接口,
RandomAccess提供了快速访问的功能。
接下来从ArrayList的构造方法说起!有三种构造方法,传参/不传参/传入一个集合
//先来说传参的情况
//传入一个数组初始容量
public ArrayList(int initialCapacity){
//判断如果>0,则将存储数据的数组长度设为传入的大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//如果==0,则会将elementData数组设为默认的空数组。
//上面介绍过了什么情况下用短的空数组,具体的后面再详细解释
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果<0则抛异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
//不传参的情况
public ArrayList() {
//不传参的情况下,则直接将数组初始化为空
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入一个集合
private int size;//数组的大小
public ArrayList(Collection<? extends E> c) {
//将集合类型转换为数组
elementData = c.toArray();
//集合大小赋值并且判断是否为0
if ((size = elementData.length) != 0) {
//因为子类重写父类方法时,可以修改返回值类型,就有可能不是object类
//而Arrays.copyOf是依赖于第一个参数的,所以要防止Arrays.copyOf不返回object类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果c集合的长度为0,则赋值空数
this.elementData = EMPTY_ELEMENTDATA;
}
}
//传入一个要插入的元素,默认插入的位置是集合尾部
public boolean add(E e) {
//插入数据之前会先调用方法判断是否需要扩容
ensureCapacityInternal(size + 1); // 添加操作会增加modcount!!
//将数据插入到集合末尾
elementData[size++] = e;
//返回插入成功
return true;
}
/**
* minCapacity,最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//接受到calculateCapacity计算得出的数组大小后
private void ensureExplicitCapacity(int minCapacity) {
//增加会让modCount++
//用于判断迭代遍历的时候判断是否遍历过程中是否发生变化
//涉及到了fail-fast机制,如果遍历过程中modCount发生变化则会失败停止遍历
modCount++;
//如果最小的容量大小要大于数组的长度
if (minCapacity - elementData.length > 0)
//则要进行扩容
grow(minCapacity);
}
//用于计算最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断数组是否是默认空数组,也就是new ArrayList()创建的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回扩容后的大小
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果是通过其他方法创建的ArrayList,如传入了参数,或者集合,则返回传入的大小
return minCapacity;
}
//扩容方法
private void grow(int minCapacity) {
//记录扩容之前的容量
int oldCapacity = elementData.length;
//扩容后的新容量大小,旧容量+旧容量/2,也就是1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量还是小于所需要的最小容量,则将新容量大小赋值为最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大小大于所能分配的最大容量,则新容量大小根据所需最小容量重新计算
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 扩容结束,将新容量复制给原始数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
//当新容量大小大于所能分配的最大容量,重新计算的方法
private static int hugeCapacity(int minCapacity) {
//如果最小容量<0,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断是否大于所能分配最大容量,是则返回Integer的最大值,否则返回能分配的最大容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
//在指定位置插入指定元素
public void add(int index, E element) {
//用于判断是否下标越界
rangeCheckForAdd(index);
//判断是否需要扩容
ensureCapacityInternal(size + 1);
//调用System.arraycopy的方法,将index之后元素往后移动一位
//elementData数组,index指定位置,elementData目标数组, index + 1开始位置, size - index 长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在index指定位置上插入元素
elementData[index] = element;
//数组大小+1
size++;
}
//方法用于判断是否下标越界,抛出异常,add/addAll用的版本
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//添加一个集合的元素,默认从尾部开始
public boolean addAll(Collection<? extends E> c) {
//类型转换成数组
Object[] a = c.toArray();
//新添加的数据的长度
int numNew = a.length;
//判断是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//从a数组的0位置开始复制长度为numNew的数据到elementData数组的size位置开始
System.arraycopy(a, 0, elementData, size, numNew);
//数组的实际长度
size += numNew;
//返回集合是否为0
return numNew != 0;
}
//添加集合数据到指定位置
public boolean addAll(int index, Collection<? extends E> c) {
//判断是否下标越界
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//需要移动的长度
int numMoved = size - index;
//如果>0
if (numMoved > 0)
//将elementData数组index开始的位置长度为numMoved的数据拷贝到elementData的index + numNew位置
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将a数组的数据拷贝到elementData数组的index开始的位置,长度为numNew。
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}