本节目标:阅读面试中常问的 ArrayList 八股对应的源码,了解其背后的代码逻辑
注意,本次阅读的源码是 JDK 1.8 版本
ArrayList 常问八股
ArrayList 是 List 的动态数组实现,它本身并不是线程安全的,因此性能相比于线程安全的 list 实现如 vector,copyWriteArrayList 要好很多。
ArrayList 中可以存储任何类型的对象,包括 null 值
其中我们常将 arraylist 和 LinkedList 进行对比,分别从以下几个方面来看
- 底层数据结构,arraylist 是使用的数组实现可以快速访问,而 LinkedList 是使用的双向链表,通过节点间的指针进行元素访问。
- 插入和删除的操作效率不同,arraylist 由于底层数据结构是数组,因此如果是在尾部插入或者删除,效率为 O (1), 其余位置皆为 O (n)。而 linkedList 由于底层数据结构为双向链表,因此对于首尾的插入删除效率为 O (1),其余位置皆为 O (n)
- 随机位置的访问效率,arraylist 由于是数组,因此随机访问的效率为 O (1), 而 linkedlist 由于是链表,随机访问为 O (n)
- 空间占用,arraylist 由于底层是数组,因此在创建时需要分配一段连续的内存空间,因此会占用较大的空间;而 linkedlist 每个元素只需要分配指针和元素,相对而言较小一些
- 适用场景,arraylist 适用于读较多的场景,linkedlist 相比而言适用于频繁的插入删除操作
- 线程安全,两者都不是线程安全的
ArrayList 的扩容机制
当 arraylist 在添加元素时,若当前元素数量已经达到了内部数组的上限,就会触发扩容机制,其扩容过程如下
- 计算新的容量,一般会扩大为 2 原容量的 1.5 倍,然后检查是否超过了最大容量限制
- 创建新数组,根据新的容量创建一个新的数组
- 将原数组元素复制到新数组中去
- 更新 arraylist 的数组引用
- 完成扩容
以上就是面试的 Arraylist 较为常见的八股,接下来我们就来阅读对应的源码,来理解其中的代码逻辑
源码阅读
首先针对第一个常问八股,ArrayList 的底层数据结构,我们来看下对应的构造函数和属性变量。
其源代码如下
Java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity. */
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances. */
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains). * * @serial
*/
private int size;
/**
* Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative */
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
...................................................
}
- 首先注意看 ArrayList 继承自AbstractList,实现了List< E >, RandomAccess, Cloneable, java.io.Serializable 等多个接口,这些依次表明 ArrayList 是一个 List,可以进行随机访问,可以进行深/浅拷贝,可以序列化。
- 接着我们看其中定义了默认容量
DEFAULT_CAPACITY, 空数组EMPTY_ELEMENTDATA, 默认容量空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 这两个数组都是初始化了的。 - 然后有一个未初始化 的数组
elementData,以及一个 size 变量
然后 ArrayList 提供了三种构造函数,分别是
Java
/**
* Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative */
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
- 当构造函数传入初始化变量
initialCapacity时- 若其>0, 则未初始化的数组变量
elementData进行数组初始化,其大小就为initialCapacity - 若其== 0,则令未初始化的数组变量
elementData指向已经初始化且 size 为 0 的EMPTY_ELEMENTDATA - 若其<0, 则抛出异常
- 若其>0, 则未初始化的数组变量
- 当构造函数为传入任何变量时
- 则令未初始化的数组变量
elementData指向已经初始化且 size 为 0 的DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 则令未初始化的数组变量
- 当构造函数传入的是指定的
collection元素的列表时- 令属性
size的值等于列表的长度 - 当 size!=0
- 若传入 collection 的类别是 ArrayList, 则令
elementData指向 collection 对应元素地址 - 若类型不等,则创建一个新数组将 collection 的元素复制过去,并令
elementData指向新数组
- 若传入 collection 的类别是 ArrayList, 则令
- 当 size== 0 时
- 则令
elementData指向初始化且 size 为 0 的EMPTY_ELEMENTDATA
- 则令
- 令属性
以上就是 ArrayList 的初始化过程
接下来我们就来看 ArrayList 的扩容过程,这个就得和 add 元素相关联了,其源码如下
Java
/**
* Appends the specified element to the end of this list. * * @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 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;
}
- 通过上述代码我们可以看到
add函数在将新元素放入elementData数组之前,先要调用ensureCapacityInternal函数确保数组的容量足够,传入的参数为size+1,也就是要想添加新元素,当前元素集合至少需要多大的容量 ensureCapacityInternal内部再调用ensureExplicitCapacity函数,而ensureExplicitCapacity调用之前,先要调用calculateCapacity函数计算容量calculateCapacity函数- 首先会判断当前数组是否为空,即也就是说判定当前是否在添加第一个元素,
- 若是,则返回传入的传入的
minCapacity和DEFAULT_CAPACITY的最大值,DEFAULT_CAPACITY默认为 10,这里也就表明 ArrayList 的初始容量大小为 10.
- 若是,则返回传入的传入的
- 否则,直接返回
minCapacity最为当前数组的大小
- 首先会判断当前数组是否为空,即也就是说判定当前是否在添加第一个元素,
- 接着来看
ensureExplicitCapacity函数- 首先
modCount++, 这个数值表示数组修改的次数 - 然后判定当前需要的容量大小与数组容量的差值
- 若数组容量<需要的最小容量,调用
grow函数进行扩容 - 否则,元素组保持不变
- 若数组容量<需要的最小容量,调用
- 首先
- 最后我们来看扩容函数
grow- 首先会根据当前数组容量大小进行计算一个新的待定容量值
newCapacity = oldCapacity + (oldCapacity >> 1), 新值为数组容量的 1.5 倍,这行代码可以看出 - 接着判定这个待定新值
newCapacity和需要的容量minCapacity进行大小比较- 若小于,则新值
newCapacity = minCapacity;
- 若小于,则新值
- 接着再判定这个新值是否超过了 ArrayList 的默认数组大小上限
MAX_ARRAY_SIZE, 即Integer.MAX_VALUE - 8- 若是,则调用
hugeCapacity函数,令newCapacity=MAX_ARRAY_SIZE
- 若是,则调用
- 最后
Arrays.copyOf, 创建一个新数组,大小为newCapacity, 并将元素组元素复制过去,让elementData指向这个新的数组。
以上就是 Arraylist 的完整扩容过程。
- 首先会根据当前数组容量大小进行计算一个新的待定容量值