提问
- 基于什么实现的?数组?链表?队列?
- 为什么能一直
add
元素?
分析
实现方式
定义的变量:
维护了一个数组:
transient Object[] elementData; // non-private to simplify nested class access
private int size;
ArrayList
内部所有的add、remove、set、get
都是对elementData
这个数组进行操作,所以ArrayList
是基于数组实现的没错了。
两个长度:size
====>当前列表的长度 elementData.length
====>数组长度
数组的长度 ≥ List的长度
默认长度和两个默认数组:
/**
* 默认的数组长度,当我们直接创建一个ArrayList对象时,容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 直接创建无参ArrayList时,内部指向该数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 创建带初始长度的ArrayList,或者传入另一个列表为参数创建对象时,如果长度为0或者传入列表长度为0,内部指向该数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
构造方法
ArrayList
定义了三个构造方法,分别为无参构造,传入一个int
类型参数构造方法,传入一个列表作为参数构造方法。
- 传入初始长度,这个方法一般用在我们知道列表长度的情况下,避免申请过多无用内存空间
public ArrayList(int initialCapacity) {
//如果长度大于0,则创建该长度的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//长度为0,指向默认数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 无参构造,将
elementData
指向默认数组,长度为0 - 传入一个列表。通过
Arrays.copyOf()
方法将传入的列表copy到新的数组,然后elementData
指向该地址
操作方法
各个方法的内部实现,都是对数组的操作
get
直接取出数组对应下标的值
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
set
同样的对数组操作,将数组中该下标对应的值替换为新的值
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
add
下标为当前size+1
,将需要add的值设置为数组中该下标对应的值。因为数组长度是固定的,所以其中涉及到最核心重要的扩容增长策略,ensureCapacityInternal(size + 1)
方法
public boolean add(E e) {
//扩容算法,传入当前长度+1
ensureCapacityInternal(size + 1); // Increments modCount!!
//扩容完成后赋值
elementData[size++] = e;
return true;
}
addAll
方法,首先将列表参数转为数组,然后使用System.arraycopy方法将该数组拷贝到elementData
中,其中同样涉及到扩容增长策略,只是传入的参数不一样。
public boolean addAll(Collection<? extends E> c) {
//先转为数组
Object[] a = c.toArray();
int numNew = a.length;
//扩容算法,传入当前长度+需要add的元素的数量
ensureCapacityInternal(size + numNew); // Increments modCount
//拷贝数组
System.arraycopy(a, 0, elementData, size, numNew);
//列表长度修改
size += numNew;
return numNew != 0;
}
remove
同样是对数组进行了拷贝操作,类似于将数组中需要移除的元素后每一个元素向前移动一位,覆盖掉原来的值。
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
//数组拷贝
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
扩容
前面add
和addAll
方法中,会有 ensureCapacityInternal1
方法进行扩容算法
先看扩容相关代码:
//①传入列表长度值
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//②如果计算后需要的长度大于当前数组的长度,执行扩容操作
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//③对数组进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
//获取当前长度
int oldCapacity = elementData.length;
//计算新长度为旧长度 * 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新长度小于需要的长度,使用传入的长度,适用于第一次创建后添加元素的扩容和addAll方法元素很多超过原有1.5倍的情况下
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果计算后长度大于最大列表长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过copyOf方法将原有数据拷贝到一个新的数组对象中,再赋值给elementData,至此,扩容完成。
elementData = Arrays.copyOf(elementData, newCapacity);
}
分析:
- 首先我们传入了一个值,代表我们列表在add或者addAll之后的长度
- 不能每次add就扩容一次数组,否则花销太大,所以对数组的扩容不会每次长度只增加一。
- 如果当前为空数组,取默认值和传入值中的最大值。为了使第一次直接创建长度为10的数组,否则增加一个元素扩容一次,花销大
- 计算是否需要进行扩容
- 扩容操作,正常情况下,数组容量扩充1.5倍,两种特殊情况:
- 第一次add元素,直接扩容为默认长度10
- addAll元素很多,加起来长度超过原有1.5倍,直接扩容到该长度
- 通过数组拷贝方法将原有所有数据拷贝到扩容后的新数组中,最后将该数组赋值给elementData
- 扩容完成
总结
- ArrayList是基于数组实现的,数组默认长度是10,所有操作都是对所维护的数组的操作
- add、addAll方法都有可能触发数组扩容
- 长度不够时,扩容长度一般为现有长度的1.5倍
- 扩容、删除等都是通过拷贝数组方式实现的,所以列表不要太长,否则每次的拷贝对性能消耗太大!