在日常使用当中,ArrayList使用率非常频繁,它基于数组的线性结构,由于添加时都是向末端添加且是连续的所以它表现为有序性,每个元素都对应一个下标,通过下标来获取数据,所以时间复杂度表现为O(1),不受元素的多少影响。在存储上,它是连续性的,所以在存放ArrayList时,需要一块没有碎片的完整的内存区域用来存放ArrayList,并且该内存的大小需要大于等于ArrayList的大小。
/**
* 默认初始容量。
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
*用于默认大小的空实例的共享空数组实例。我们
*将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少
*添加第一个元素。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*
存储ArrayList元素的数组缓冲区。
* ArrayList的容量是这个数组缓冲区的长度。任何
*使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA清空ArrayList
*将在添加第一个元素时扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的大小(它包含的元素的数量)。
*
* @serial
*/
private int size;
/**
* 构造一个初始容量为10的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造具有指定初始容量的空列表。
*
* @param 初始容量列表的初始容量
* @throws IllegalArgumentException 如果指定的初始容量为负
*
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//参数大于0 创建大小为initialCapacity的空数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//参数等于0 默认创建为10大小的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 将指定的元素追加到此列表的末尾。
*
* @param e 元素添加到此列表
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
add 方法第一个就调用了ensureCapacityInternal 方法 ,size(注意这里不是数组的大小 而是数组包含了多少个元素)加一传给了该方法
private void ensureCapacityInternal(int minCapacity) {
//如果数组大小为空,则取默认值10和参数minCapacity中的最大值 这里我们把minCapacity比作元素的数量,这里需要保证数组的大小足够容纳所有元素。求得最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
该方法主要是取最大值作为初始数组的大小
接着我们看ensureExplicitCapacity方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里对modCount加一,我们来看看官方对modcount的定义
/**
*此列表被结构修改的次数。
*结构修改是指改变的大小
*列表,或者以迭代的方式扰乱它
*进度可能会产生不正确的结果。
*
*
该字段由迭代器和列表迭代器实现使用
*由{@code iterator}和{@code listIterator}方法返回。
*如果该字段的值发生意外更改,则迭代器(或列表)
将抛出一个{@code ConcurrentModificationException}
*响应{@code next}, {@code remove}, {@code previous},
* {@code set}或{@code add}操作。这提供了
* fail-fast行为,而不是非确定性行为
*迭代过程中并发修改的面。
*
*
使用该字段的子类是可选的。如果是子类
*希望提供故障快速迭代器(和列表迭代器),然后它
*只需要在它的{@code add(int, E)}和中增加这个字段
* {@code remove(int)}方法(以及它覆盖的任何其他方法)
*导致对列表的结构修改)。打给
* {@code add(int, E)}或{@code remove(int)}不能添加超过
*一个到这个字段,否则迭代器(和列表迭代器)将抛出
*伪{@code concurrentmodificationexception}。如果一个实现
*不希望提供故障快速迭代器,此字段可能是
*忽略。
*/
protected transient int modCount = 0;
通过描述我们知道该变量是用来表示该数组的结构发生了几次变化,说白了就是该数组的大小被扩展了多少次
如果需扩展的大小大于本身的数组大小则调用grow方法
/**
*增加容量,以确保它至少可以容纳
*由最小容量参数指定的元素数量。
*
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//将原来的数组大小除以2在加上原来的数组大小,说白了就是自身扩大了1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//预扩展后的大小必须大于所需的最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//预扩展后的大小大于MAX_ARRAY_SIZE,则根据最小容量比对MAX_ARRAY_SIZE 大返回Integer.MAX_VALUE 小则返回MAX_ARRAY_SIZE
//这里确保数组大小最大只能为Integer.MAX_VALUE,考虑到有些虚拟机会保留些头信息,所以尽量最大值取MAX_ARRAY_SIZE 也就是Integer.MAX_VALUE上减8
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity通常接近于size,所以这是一个优势
//复制elementData数组并扩充大小到newCapacity大小,返回新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 要分配的数组的最大大小。
*有些虚拟机在数组中保留一些头信息。
*尝试分配更大的数组可能会导致
* OutOfMemoryError:请求的数组大小超过VM限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
elementData[size++] = e;
最后将新的元素e放到该数组最后一个元素的末尾
总结
/**
* 返回列表中指定位置的元素。
*
* @param index要返回的元素的索引
* @return 位于列表中指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
*检查给定索引是否在范围内。如果没有,则抛出一个适当的
*运行时异常。此方法不*不*检查索引是否为
*否定:它总是在数组访问之前使用,
*如果索引为负,则抛出ArrayIndexOutOfBoundsException。
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
总结
/**
*从列表中移除指定元素的第一个出现项,
如果有的话。如果列表不包含该元素,则它包含
*不变。更正式地说,删除索引最低的元素
* i这样
* * (o==null ? get(i)==null : o.equals(get(i)))
*(如果存在这样的元素)。返回true如果这个列表
*包含指定的元素(或相等,如果此列表
*由于调用而更改)。
*
* @param o 元素,如果存在,则从该列表中删除
* @return true 如果此列表包含指定的元素
*/
public boolean remove(Object o) {
//如果o为null,就把数组所有的元素都清空
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//遍历循环所有元素,如果当前元素相同,就删除集合当中该元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
//没有找到相同元素返回false
return false;
}
我们看看fastRemove方法
/*
* Private remove method 该方法跳过边界检查,但不跳过
*返回删除的值。
*/
private void fastRemove(int index) {
//数组结构变量加1
modCount++;
//计算离最后一个元素中间有多少个元素
int numMoved = size - index - 1;
//如果numMoved大于0 就将numMoved后面的元素复制到index的前面,目的是为了让要删除的元素始终在末尾
//如果要移除的原始刚好在末尾,则忽略复制的性能消耗
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
}
/**
* 将指定元素插入其中的指定位置
*列表。将当前位于该位置(如果有)的元素移动,并
*右边的任何后续元素(将一个元素添加到它们的索引中)。
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//下标越界校验,越界抛出IndexOutOfBoundsException
rangeCheckForAdd(index);
//上文说过,这里是对数组大小初始化的判定,数组大小不足够容纳下一个元素就自身容量大小扩充1.5倍
ensureCapacityInternal(size + 1); // Increments modCount!!
//插入的index后的所有元素往后面挪一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//空出的下标index赋值element
elementData[index] = element;
//元素大小自增一
size++;
}
/**
* 将指定集合中的所有元素追加到
*此列表,按其返回的顺序排列
指定集合的迭代器。这个操作的行为是
*如果在操作期间修改了指定的集合,则未定义
*正在进行中。(这意味着这个调用的行为是
*如果指定的集合是这个列表,则未定义
*列表非空。)
*
* @param c 集合,其中包含要添加到此列表中的元素
* @return true 如果此列表因调用而更改
* @throws NullPointerException 如果指定的集合为空
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
//需要插入的集合的大小
int numNew = a.length;
//计算两个集合合并的大小来初始化该集合的大小(最小容量无法满足就自身扩充1。5倍)
ensureCapacityInternal(size + numNew); // Increments modCount
//新的集合直接复制到该集合的末尾
System.arraycopy(a, 0, elementData, size, numNew);
//自身大小自加上插入集合的大小
size += numNew;
return numNew != 0;
}
总结