DEFAULT_CAPACITY
java
默认初始化容量,表明 elementData
数组的长度面试
EMPTY_ELEMENTDATA
数组
构造空 ArrayList
实例时,赋值给 elementData
的空数组实例app
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
函数
构造默认容量 ArrayList
实例时,赋值给 elementData
的空数组实例ui
elementData
this
ArrayList
实际存储元素的动态数组spa
size
设计
ArrayList
中实际容纳元素的个数3d
public ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//当赋值给 ArrayList 的容量为 0 时,令 elementData = EMPTY_ELEMENTDATA;
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
复制代码
public ArrayList()
public ArrayList() {
//当构造默认容量的 ArrayList 时,令 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码
public ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
// 将源集合的数据转化为 array 赋值给 elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
复制代码
add(E e)
public boolean add(E e) {
// 保证添加元素后, ArrayList 不溢出
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
复制代码
该方法调用了 ensureCapacityInternal
保证扩容后不会溢出。
ensureCapacityInternal(int minCapacity)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
复制代码
calculateCapacity(Object[] elementData, int minCapacity)
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
复制代码
该方法会返回添加元素后最小的 capacity 的值。若是初始化 ArrayList
时未指定容量,那么这个最小值将会是 DEFAULT_CAPACITY
。
ensureExplicitCapacity(int minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0) {
//若是添加元素后,数组的容量会大于现有数组的 length。须要将数组扩容
grow(minCapacity);
}
}
复制代码
这里维护了 modCount
的改变。
grow(int minCapacity)
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 1. 当 ArrayList 第一次放入 元素时,扩容后新的数组容量为 1 或者 10;
// 2. 当 ArrayList 添加元素后容量大于 MAX_ARRAY_SIZE 时,容量会变为 MAX_ARRAY_SIZE 或 Integer.MAX_VALUE
// 3. 其它状况下,扩容操做使数组容量变为原来的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 若新增元素后,oldCapacity * 1.5 > MAX_ARRAY_SIZE,调用 hugeCapacity 方法计算容量
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码
这里看到 ArrayList
扩容后新数组的容量有三种状况。因此面试被问到 ArrayList
扩容后容量怎样变化时,最好将三种状况都说出来,而不仅是回答变为原容量的 1.5 倍。
add(int index, E element)
public void add(int index, E element) {
// add 方法的索引范围校验
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 调用 System.arraycopy 将数组索引不小于 index 的元素后移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
复制代码
remove(int index)
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
}
// 将最后一位元素置空,便于 GC 回收
elementData[--size] = null;
return oldValue;
}
复制代码
remove(Object o)
public boolean remove(Object o) {
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;
}
}
}
return false;
}
复制代码
分为空元素和非空元素两种状况遍历。
删除 ArrayList
中出现的第一个等于 o
的元素,查找到时,调用 fastRemove
方法删除。
fastRemove
方法移除了索引越界检查以及返回值的保存。
removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
复制代码
调用批量删除方法 batchRemove
,注意这里传入的布尔类型参数值为 false
。
batchRemove(Collection<?> c, boolean complement)
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r 表示 elementData 中待读取的下一个数据对应的索引;w 表示待写入的下一个索引
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++) {
// 若是是删除集合中的元素,则将未出如今 c 中的元素写入到索引为 w 的位置;不然将出如今 c 中的元素写入到索引为 w 的位置。
if (c.contains(elementData[r]) == complement) {
elementData[w++] = elementData[r];
}
}
} finally {
// 保留与 AbstractCollection 行为的兼容性
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// 将已写入元素以后的元素置为 null
for (int i = w; i < size; i++) {
elementData[i] = null;
}
// modCount 增长次数为 删除的元素个数
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
复制代码
该方法设计的很巧妙。
设置了 r
和 w
两个变量来控制数组的已读标记和写入标记。r
表示已经遍历过的数据的索引,w
表示已写入的数据的索引。(我的感受相似于 Netty
中 ByteBuf
的设计)
经过 complement
变量来控制写入的数据是不是集合 c
中出现过的数据。若是 complement == true
,则写入的元素为该 ArrayList
实例 与集合 c
中共同存在的元素;若是 complement == false
,则写入的元素为该实例元素去除集合 c
中元素的结果。这里是用来删除集合 c
中的元素,因此传入的变量值为 false
。
在 finally
块中,将 w
及以后的元素都置为 null
。只留下符合要求的元素。同时,更新 modCount
的次数。
set(int index, E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
复制代码
修改操做很简单,在数组赋值的基础上增长了索引值校验,注意该方法的返回值为该索引位上的旧值。
get(int index)
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
复制代码
查找操做同修改操做同样,在数组查找操做上增长索引校验。
clone()
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
复制代码
这里元素的拷贝调用了 Arrays.copyOf
方法,该方法是浅拷贝,因此 clone
方法呈现的结果也会是浅拷贝,对于拷贝出来的元素进行修改,会同时修改原 ArrayList
中的元素。
ArrayList
的方法都比较简单,可是其中也有很多细节须要注意。
ArrayList
底层实现是动态数组,因此特色是元素的查找操做快,在末尾插入和删除元素也很快,时间复杂度都是 O(1)。而在中间插入和删除元素的均摊时间复杂度为 O(n) 。
ArrayList
扩容时,其中的 elementData
容量变化会有三种状况:
1. 当 `ArrayList` 第一次放入 元素时,扩容后新的数组容量为 1 或者 10;
2. 当 `ArrayList` 添加元素后容量大于 `MAX_ARRAY_SIZE` 时,容量会变为 `MAX_ARRAY_SIZE` 或 `Integer.MAX_VALUE`
3. 其它状况下,扩容操做使数组容量变为原来的 1.5 倍
复制代码
ArrayList
的 clone
方法是浅拷贝。对拷贝出来的实例中的元素进行修改,会改变原来的 ArrayList
实例。