ArrayList 内部采用数组实现,是一种顺序存储方式,并且支持随机访问。本文分析基于 JDK 1.8.0_151 版本。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
ArrayList
继承于AbstractList
并且实现了List
,RandomAccess
,Cloneable
,Serializable
接口。
属性
// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始化容量(只有执行 add 操作时才执行初始化)
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 以上两个空数组只是为了区别于调用不同的构造方法
// 存放数据的内部数组(不参与序列化)
transient Object[] elementData;
// ArrayList 中有效数据的个数(并不一定就是elementData.length)
private int size;
// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
方法
构造方法
// 构造具有指定初始容量的空列表
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);
}
}
// 构造空列表
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 构造一个包含指定 collection 的元素的列表,这些元素是按照该collection 的迭代器返回它们的顺序排列的
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 该 bug 分析见 http://blog.csdn.net/x_iya/article/details/78313756
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// c.toArray 不一定返回 Object[],也有可能是 String[](List<String> list = new ArrayList<>(Arrays.asList("111", "222"));)
if (elementData.getClass() != Object[].class)
// 转换为Object[]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
增
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
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;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
在执行add
相关操作时,需要调用ensureCapacityInternal
方法以确保elementData
有足够的空间保存元素。
删
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
改&查
ArrayList
的修改和获取元素的方法相当简单,就是对elementData
数组进行相应的操作罢了。
改
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
查
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
其他
扩容
扩容操作基本上是以数组作为其存储结构的类的核心了(String、ArrayList、StringBuilder,StringBuffer)。其他的所有操作无非就是围绕这个可变长的数组。
调用关系:
ensureCapacity() //可以由用户显式调用
ensureCapacityInternal() // 由 ArrayList 内部隐式调用
--> ensureExplicitCapacity()
--> grow()
grow 方法:
/**
* 增加容量以确保它至少可以容纳最小容量参数 minCapacity 指定的元素数量
* minCapacity: 数组需要的最小容量值
* newCapacity: 就是扩容后实际的容量值
* 每次自动扩容都是扩大原有容量 1.5 倍
* 当自动扩容后的容量值仍然小于所需要的容量时,就直接扩容到所需容量值
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 可以看到首先扩展为原来的 1.5 倍
if (newCapacity - minCapacity < 0) // 防止溢出
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 设置大小为 Integer.MAX_VALUE
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
ensureExplicitCapacity 方法:
/**
* 保证扩容值大于原数组长度才执行 grow() 方法
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureCapacityInternal 方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
ensureCapacityInternal()方法首先判断该数组此时是不是默认初始数组,如果是的话就判断一下minCapacity是否大于10,如果不是就扩容到初始默认数组长度10,如果是就直接扩容到minCapacity值的长度。
用户所能使用的只有一个公有方法,也就是说可以由用户显式执行扩容操作:
/**
* 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数 minCapacity 所指定的元素个数
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
而ensureCapacityInternal
隐式扩容方法在容器添加元素的时候被调用,也就是“懒扩容–>只有在真正添加元素的时候进行扩容操作”。
使用 ArrayList 需要注意的地方:
- 1.ArrayList 是基于数组的方式实现的,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
- 2.ArrayList在插入元素时,可能会进行数组的扩容,但是在删除元素时却不会减小数组的容量,如果希望减小数组的容量,可使用 trimToSize 方法,在查找元素要遍历数组时,对非null元素使用equals方法,对null元素使用==。
- 3.扩充容量的方法 ensureCapacityInternal。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当 容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接把新容量设置为传入的参数(也就是所需的容 量),而后用Arrays.copyof()方法将元素拷贝到新的数组。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList
- 4.ArrayList不是线程安全的。
参考:
https://zhuanlan.zhihu.com/p/27873515
https://zhuanlan.zhihu.com/p/28545284
https://mjd507.github.io/2017/04/09/Data-Structure-ArrayList-SourceCode/