我们都知道:ArrayList
是一个有序的、元素可重复的集合。那么 ArrayList
的底层到底是通过什么来存储元素的呢 ?
通过查看 ArrayList
的源码,我们可以看到在 ArrayList
的定义中有这样一个成员变量 —— elementData
:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* 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
// ......
}
通过注释,我们也不难看出,这个 Object
数组就是 ArrayList
底层用于存储元素的那个数组。
也就是说,ArrayList 的底层其实是一个 Object
数组在存储数据。这也就解释了,为什么 ArrayList
是一个有序的、元素可以重复的集合,因为这本质上就是数组的特点。
通常,我们使用 ArrayList
集合时,都会直接 new 一个无参的构造方法:
ArrayList<Integer> list = new ArrayList<>();
那么这段代码发生了什么呢 ?
首先来看无参构造方法 ArrayList()
:
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从注释可以得知:调用无参构造方法 ArrayList()
会创建一个初始容量为 10 的空 List
集合。
事实真的是这样吗?再来看看这个 DEFAULTCAPACITY_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 = {
};
很显然,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是一个空数组,那么为什么前面说会创建一个初始容量为 10 的 List
集合 ?
这个疑问,需要由 add()
方法来解答。
当我们往集合中直接添加一个元素时,调用的是下面的这个方法:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
modCount++; // 记录操作 ArrayList 集合的次数
add(e, elementData, size);
return true;
}
在 add()
方法中,会调用另一个需要三个参数的 add()
方法来执行添加元素的过程,其方法体如下:
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 判断 elementData 数组中当前元素的个数是否等于 elementData 数组的容量
elementData = grow(); // 如果相等,则调用 grow() 方法,进行扩容
elementData[s] = e; // 给对应位置的索引赋值
size = s + 1; // elementData 已存储元素个数 +1
}
在上面的这个方法中,有一个很重要的方法 ,就是 grow()
,它负责数组的扩容:
private Object[] grow() {
return grow(size + 1); // 返回 grow(size+1) 的结果
}
在这个 grow()
方法中,实际调用了一个有参的 grow()
方法:
/**
* 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
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
在上面的这个方法中,我们可以看出来:ArrayList
所谓的扩容,其实就是将原来的数组中的内容,复制到一个新的更大的数组中。
这个方法中,又调用了 newCapacity()
方法,这个方法返回的就是扩增后的容量:
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by 50% if that suffices.
* Will not return a capacity greater than MAX_ARRAY_SIZE unless
* the given minimum capacity is greater than MAX_ARRAY_SIZE.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private int newCapacity(int minCapacity) {
// minCapacity 代表的是:数组可再插入一个元素的最小容量
// overflow-conscious code
int oldCapacity = elementData.length; // 首先获取当前 elementData 数组的存储容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 将存储容量扩容 1.5 倍
if (newCapacity - minCapacity <= 0) {
// 如果扩容后还是小于等于最小容量需求
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 判断是不是一个空数组
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0) // 扩容后容量和 MAX_ARRAY_SIZE 比较
? newCapacity // 如果小于 MAX_ARRAY_SIZE,则容量为扩容后容量
: hugeCapacity(minCapacity); // 如果大于 MAX_ARRAY_SIZE,则执行 hugeCapacity 方法
}
对于上面的方法,我们首先明确一个参数 minCapacity
,这个参数代表的是最小容量需求(比如 ArrayList
中元素个数为 9 个,如果要新增一个元素,那么 ArrayList
的容量至少为 10 才能满足需求,那么这个 minCapacity
就是 10)。
我们还可以看出,一旦进入 newCapacity()
这个方法,数组的容量就会扩为原来的 1.5 倍。如果扩容后的容量仍然小于等于 minCapacity
且 elementData
是一个空数组,那么将会以 DEFAULT_CAPACITY
和 minCapacity
的最大值作为容量。
而 DEFAULT_CAPACITY
定义为:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
这里就解开了调用无参构造方法 ArrayList()
时,集合就会被赋予初始容量 10 的这个疑问。
也就是说:当调用无参构造方法 ArrayList()
时,集合并未被赋予初始容量,只有第一次调用 add()
方法时,才会给集合赋予初始容量 10。之后,如果集合中的元素达到了容量上限时,再新增元素,集合的容量将扩容为原来的 1.5 倍。
那么集合的容量会无限的扩容吗?
当然不会!我们再看看上面的 newCapacity()
方法中有如下几行代码:
// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int newCapacity(int minCapacity) {
// minCapacity 代表的是:数组可再插入一个元素的最小容量
// overflow-conscious code
/* ...... */
return (newCapacity - MAX_ARRAY_SIZE <= 0) // 扩容后容量和 MAX_ARRAY_SIZE 比较,
? newCapacity // 如果小于等于 MAX_ARRAY_SIZE,则容量为扩容后容量
: hugeCapacity(minCapacity); // 如果大于 MAX_ARRAY_SIZE,则执行 hugeCapacity 方法
}
当扩容后的新容量 newCapacity
小于 MAX_ARRAY_SIZE
时,会 newCapacity
作为新的容量返回,否则以 hugeCapacity
方法的返回值作为新的容量返回。
hugeCapacity()
方法定义如下:
// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow // 如果最小容量需求小于0,则抛出内存溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) // 最小容量需求和 MAX_ARRAY_SIZE 比较
? Integer.MAX_VALUE // 如果大于 MAX_ARRAY_SIZE,则以 Integer.MAX_VALUE 作为新容量
: MAX_ARRAY_SIZE; // 否则,返回 MAX_ARRAY_SIZE 作为新容量
}
至此,关于 add()
方法的主要流程,我们已经全部摸清。
弄清了 add()
方法,我们也就明白了 ArrayList
的扩容机制。
总结下来 ArrayList
的扩容机制就是:当调用无参构造方法 ArrayList()
时,集合并未被赋予初始容量,只有第一次调用 add()
方法时,才会给集合赋予初始容量 10。之后,如果集合中的元素达到了容量上限时,再新增元素,集合的容量将扩容为原来的 1.5 倍。但是 ArrayList
的容量并不会无限扩容,如果扩容后的新容量大于 MAX_ARRAY_SIZE
,最大的新容量将只能扩为 Integer.MAX_VALUE
,如果再超出容量,将会抛出内存溢出错误。
get()
方法的作用是获取对应下标位置的元素。它的实现也非常简单:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
*/
public E get(int index) {
// 1. 首先检查 index 是否越界
Objects.checkIndex(index, size);
// 2. 返回下标 index 对应的元素
return elementData(index);
}
set()
方法的作用就是更新对应下标位置的元素。它的实现同样非常简单:
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
*/
public E set(int index, E element) {
// 1. 检查 index 是否越界
Objects.checkIndex(index, size);
// 2. 获取 index 位置的旧元素
E oldValue = elementData(index);
// 3. 新元素覆盖旧元素
elementData[index] = element;
// 4. 返回旧元素
return oldValue;
}
关于 remove()
方法,有 remove(Object o)
和 remove(int index)
,此处讲解的是 remove(int index)
方法。
remove()
方法的作用就是从 List
集合中移除指定下标的元素。它的实现如下:
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
*/
public E remove(int index) {
// 1. 检查 index 是否越界
Objects.checkIndex(index, size);
final Object[] es = elementData;
// 2. 获取旧值
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
// 3. 执行删除
fastRemove(es, index);
// 4. 返回删除的值
return oldValue;
}
在 remove()
方法中,实际执行删除操作的是 fastRemove()
方法:
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(Object[] es, int i) {
// 1. 操作次数 +1
modCount++;
final int newSize;
// 2. 要删除元素,给 size-1,得到 newSize。判断 newSize 是否大于要删除元素索引
if ((newSize = size - 1) > i)
// 3. 如果 i 有效,则拷贝 es 的 i+1 之后的元素到 es 的 i 之后
// 也就是相当于 es 数组 i 下标之后的元素整体左移一位
System.arraycopy(es, i + 1, es, i, newSize - i);
// 4. 由于 es 数组 i 下标之后的元素左移了一位,因此将 es 数组的最后一位置空
es[size = newSize] = null;
}
从源码我们可以看出来,ArrayList
集合删除指定下标位置元素的思路就是:将待删除元素之后的元素全部向前移动一位拷贝到原数组中,最后将原数组最后一位置空,即可完成元素的删除操作。
而 remove(Object o)
方法的核心思路其实和 remove(Object[] es, int i)
完全一致:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* {@code Objects.equals(o, get(i))}
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
// 1. 找到指定元素第一次出现的下标
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
// 2. 执行删除动作
fastRemove(es, i);
return true;
}
其思想就是:首先找到待删除元素第一次出现的下标,然后调用 fastaRemove()
方法最终执行删除动作。
从阅读 ArrayList
集合的源码的整个过程下来,我发现ArrayList
类最复杂的代码其实就是 add()
方法部分。而 add()
方法中最复杂的代码就是 ArrayList()
的扩容问题。至于 get()
、set()
、remove()
方法,本质上都是对数组的基本操作,很容易理解。