ArrayList
ArrayList最早出现在 JDK 1.2中,底层基于数组实现,它是一个动态数组列表结构的容器。
- 元素有序,可重复
- 增删元素的速度慢。每次增加删除元素,都需要更改数组长度、拷贝元素及移动元素位置。
- 查询元素的速度快。底层数据结构是基于Object 数组,可以根据
地址+索引
的方式快速获取对应位置上的元素。 - 实现 Serializable 标记性接口。ArrayList 实现该标记性接口可提供为类提供序列化和反序列化功能,这意味着 ArrayList 支持序列化,能通过序列化去传输。
- 实现 Cloneable 标记性接口,提供了克隆功能。
- 实现 RandomAccess 标记性接口。为 ArrayList 提供了随机访问功能,也就是通过下标获取元素对象的功能。
- 实现 List 接口,是 List 的实现类之一。
- 继承了 AbstractList接口,可以使用
for-each
迭代。
ArrayList 的数据结构
ArrayList最早出现在 JDK 1.2中,底层基于数组实现,它是一个动态数组列表结构的容器。
源码剖析
-
成员变量
//默认容量10
private static final int DEFAULT_CAPACITY = 10;
//空对象数组。使得所有ArrayList空实例都指向同一个空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认空对象数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储ArrayList元素的数组缓冲区,ArrayList的容量是这个数组缓冲区的长度。
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList的大小
private int size;
ArrayList 构造方法
无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
jdk 1.2 ~ jdk 1.6 中,ArrayList 的确是会通过空参构造方法生成一个指定底层数据结构容量为 10 的空数组。
jdk 1.7 后,ArrayList 的空参构造方法为了避免无用内存占用,仅仅只是创建了一个底层数据结构长度为 0 的空数组。只有在初次添加元素时才将容量扩容为 10。
指定初始容量值的构造方法
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(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;
}
}
add添加方法
add(E e) 将元素添加到列表末尾方法
/**
* 将指定的元素追加到此列表的末尾。
* @param e 要添加到此列表的元素
*/
public boolean add(E e){
// 添加元素之前,先调用 ensureCapacityInternal 对内部容量进行校验
// 因为要添加一个元素,故方法里是 size + 1
ensureCapacityInternal(size +1);
// 元素添加进去实质就是给最后x一个数组元素赋值
elementData[size++]= e;
return true;
}
ensureCapacityInternal() 方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算得到最小扩容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断集合存数据的数组是否等于空容量的数组,实际就是看数组有没有存在数据
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 通过最小容量和默认容量求出最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 实际修改集合次数++ (在扩容的过程中没用,主要是用于迭代器中)
// overflow-conscious code预防溢出
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容
grow(minCapacity);
}
add添加概述:
- 当添加 1 个元素到 ArrayList 中时,如若 ArrayList 为没有存放任何元素的空集合,那么在执行
ensureCapacityInternal()
中calculateCapacity()
方法过后,minCapacity 会变为 10。此时,minCapacity-elementData.length>0
成立,会进入到grow(minCapacity)
方法。 - 当 add 添加第 2 个元素时,minCapacity 为 2,此时
elementData.length
(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity-elementData.length>0
不成立,所以不会进入grow(minCapacity)
方法。 - 倘若一直添加元素,直至添加第 11 个元素,
minCapacity-elementData.length>0
成立(即 11 - 10 > 0),进入 grow() 方法进行扩容。
grow()扩容方法
private void grow(int minCapacity) {
// 记录原数组的实际长度
int oldCapacity = elementData.length;
// 核心扩容算法,扩容后的容量为原容量的 1.5 倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容后的容量小于最小需要的容量,就把最小需要容量当作数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 检查新容量超出了ArrayList所定义的最大容量,就调用 hugeCapacity() 来比较 minCapacity
// 和 MAX_ARRAY_SIZE
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);
}
//获取最大容量 Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果 minCapacity 大于 MAX_ARRAY_SIZE,则新容量则为 Interger.MAX_VALUE,否则,新容量
// 大小则为 MAX_ARRAY_SIZE。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
add(int index, E element) 在指定索引处添加元素方法
public void add(int index, E element) {
// 添加范围检查
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
// arraycopy()方法实现数组自己复制自己
// elementData:源数组;index:源数组中的起始位置;
// elementData:目标数组;index + 1:目标数组中的起始位置;
// size - index:要复制的数组元素的数量;
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//add和addAll使用的rangeCheck版本
private void rangeCheckForAdd(int index) {
//如若超出范围则抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
ensureCapacity方法
ensureCapacity
方法ArrayList内部没有被调用过,是提供给用户调用,主要作用是:
增加ArrayList实例的容量,如果必需的,以确保它至少可以容纳元素的数量由最小容量参数指定。
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
最好在 add 大量元素之前用 ensureCapacity
方法,以减少增量重新分配的次数。
trimToSize方法
ensureCapacity
方法ArrayList内部没有被调用过,是提供给用户调用,主要作用是:
将elementData数组调整为ArrayList中实际元素个数大小的容量
/**
* 将ArrayList的实际容量调整为实际元素总个数大小,原是数组容量大小
*/
public void trimToSize() {
// modCount用来记录修改次数,是父类AbstractList中的属性
modCount++;// 修改次数加1
// 如果ArrayList中实际元素个数小于数组长度,那么就清除掉数组中空的元素,然后将数组长度设置为ArrayList中实际元素个数
if (size < elementData.length) {
// 判断ArrayList中是否没有元素,即0个元素,则将elementData设置为一个空数组
// 如果元素个数大于0,则调用Arrays.copyOf()方法重新生成一个长度为size的数组,然后赋给elementData
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
默认初始容量是0或10,当添加的元素个数大于10后,会自动扩容,容量变为原来的1.5倍,也就是说elementData数组的长度是15,但如果在添加10个元素之后,只添加了1个元素,现在ArrayList的size其实是11,但elementData数组的length却是15,那么还有4个数组空间没有利用起来,浪费资源,就可以调用该方法调整elementData数组,将其设置为length等于size的数组,底层是复制了一个长度为size的elementData数组返回。