ArrayList源码分析及扩容机制
一、ArrayList介绍:
ArrayList 的底层是数组队列(Object[]),相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。
ArrayList 不保证线程安全;
在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
构造函数:
/**
*默认无参构造函数
*/
public ArrayList() {
}
/**
* 带初始容量参数的构造函数 */
public ArrayList(int initialCapacity) {
}
/**
* 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
*/
public ArrayList(Collection<? extends E> c) {
}
二、ArrayList源码分析:
2.1 AL的父亲们:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- RandomAccess 是一个标志接口(在某些排序和搜索算法中会检查是否是继承这个标志接口),表明实现这个这个接口的 List 集合是支持快速随机访问的。
- ArrayList 实现了 Cloneable 接口 ,即覆盖了函数clone(),能被克隆。
- ArrayList 实现了 java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
2.2 AL的初始化,你不用就给你个空的:
// 初始大小
private static final int DEFAULT_CAPACITY = 10;
// 初始空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList的底层实现数组
transient Object[] elementData;
/**
无参数构造函数,可见如果我们没有给定初始值,AL只是创建了一个空数组“糊弄人”
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
通过上面我们看到了ArrayList无参初始化时并没有给定数组容量,其实这里就牵扯出了ArrayList的扩容机制:
三、ArrayList扩容分析:
3.1 add()方法,你用我我才给你开辟空间:
add()方法:(这里是我简化合并后的核心代码)
// AL 的元素个数
private int size;
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
int minCapacity = size + 1;
// 如果是默认初始化的数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取默认的容量和size + 1的较大值,+1是因为add发生时正在存入第一个元素
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果add会发生内存溢出的情况,第一次add时便会最终进入grow扩容
if (minCapacity - elementData.length > 0){
//调用grow方法进行扩容
grow(minCapacity);
}
// 这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++] = e;
return true;
}
分析一波:
其实add()方法最核心的判断就在于if (minCapacity - elementData.length > 0),我再翻译一下就是if (size + 1 - elementData.length > 0),也就是判断这次add是否会超过这时AL的容量,如果超过,那么就进入grow()方法进行扩容,第一次add()方法执行时,因为minCapacity=10 而elementData.length=0所以也会进grow()方法。
3.2 grow() :真正的扩容方法是我:
grow()方法:(同样是简化版本)
// 要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = elementData.length;
// oldCapacity /2,新容量实际是 oldCapacity * 1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 然后*1.5都不够的话,就要多少给多少,直接等于minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于了AL定义的最大容量,那就交给minCapacity来确定一个合适容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
// 复制扩容操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
至于最后的copyOf方法内部实际调用了 System.arraycopy() 方法来实现数组的复制,通过复制来扩容。
这是总结:
无参初始容量零,一次add变成10,
1.5倍不够爽,最小容量来确定,
虽然数组有上限,动态数组还是爽