ArrayList
整体架构
ArrayList就是一个数组的结构
图中展示的是一个长度为10的数组(elementData),从1开始计数。index表示数组的大小从0开始计数,接下来了解几个概念
DEFAULT_CAPACITY:数组的初始大小10
size:表示当前数组的大小,从1开始计数,没有volatile修饰,线程不安全
modCount:统计当前数组被修改的版本次数,修改一次+1
类注释
1、允许put null值,会自动扩容
2、size、isEmpty、set、get、add等方法的时间复杂度都是o(1)
3、是非线程安全的,推荐使用线程安全的Collections#synchronizedList;
4、快速失败,增强for循环,或者在使用迭代器的过程中,数组大小被改变会快速失败,抛出异常
初始化
有三种初始化方法:无参数初始化、指定数据初始化、指定大小初始化
//无参数初始化,数组大小为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定数据初始化
public ArrayList(Collection<? extends E> c) {
//elementData是保存数组的容器默认为null
elementData = c.toArray();
//如果给定的集合c有值
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652),这是java的一个bug,意思是当给定集合内的元素不是 Object 类型时,我们会转化成 Object 的类型。一般情况下都不会触发此 bug,只有在下列场景下才会触发:ArrayList 初始化之后(ArrayList 元素非 Object 类型),再次调用 toArray 方法,得到 Object 数组,并且往 Object 数组赋值时,才会触发此 bug
//如果集合元素类型不是object类型,会转成object类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 给定集合无值,则默认空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
//指定大小初始化
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
我们可以看到ArrayList无参初始化时数组默认为空,并不是10,10是在第一次add()扩容时的数组值
add()增加
步骤:1、先判断数组大小是否足够,不够进行扩容
2、进行赋值
public boolean add(E e) {
//确保数组大小足够,不够进行扩容,size是当前数组的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//进行赋值
elementData[size++] = e;
return true;
}
扩容ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
//如果初始化数组时,有给定初始值,以给定初始值为准
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保容积足够
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//数组被修改,modCount进行记录
modCount++;
// 判断是否需要扩容,如果我们需要的长度大于数组目前的长度则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 得到原来数组的长度
int oldCapacity = elementData.length;
//新的数组的长度是原来数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的值小于我们的期望值,那么扩容的值就是我们的期望值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果扩容后的值大于jvm所能分配的数组的最大值,那么就yongInteger的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将原来的数组复制拷贝到新数组下
elementData = Arrays.copyOf(elementData, newCapacity);
}
//拷贝的本质其实就是arraycopy
/*
* @param src 被拷贝的数组
* @param srcPos 从数组那里开始
* @param dest 目标数组
* @param destPos 从目标数组那个索引位置开始拷贝
* @param length 拷贝的长度
* 此方法是没有返回值的,通过 dest 的引用进行传值
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
从源码中我们可以看到,ArrayList在进行增加时会看是否需要扩容,然后再进行赋值
在扩容方面,我们可以看到
1、扩容后的数组的长度是原数组长度的1.5倍,如果我们扩容后还达不到我们的预期值那么扩容的长度就是我们的预期值
2、如果扩容的长度超过了jvm所能分配的数组的长度,那么就是用Integer的最大值。
3、扩容的本质就是复制拷贝,将原来数组拷贝到新数组
删除remove
public boolean remove(Object o) {
// 如果要删除的值是 null,找到第一个值是 null 的删除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 如果要删除的值不为 null,找到第一个和要删除的值相等的删除
for (int index = 0; index < size; index++)
// 这里是根据 equals 来判断值相等的,相等后再根据索引位置进行删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
// 记录数组的结构要发生变动了
modCount++;
// numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
// 减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起
int numMoved = size - index - 1;
if (numMoved > 0)
// 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//数组最后一个位置赋值 null,帮助 GC
elementData[--size] = null;
}