1. 简介
不管是做Android开发还是做Java开发,都会经常使用到ArrayList 。ArrayList底层基于数组实现容量大小动态变化。下面将介绍ArrayList的使用以及分析ArrayList的源码,从而对ArrayList有一个深入的理解。
2. ArrayList的使用
先看以下实例:
package zzw.cn.listtest;
import java.util.ArrayList;
/**
* @author 鹭岛猥琐男
* @create 2019/8/6 20:56
*/
public class TestArrayList
{
public static void main(String[] args)
{
//创建ArrayList
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 15; i++)
{
arrayList.add(i);//将数据添加进ArrayList
}
System.out.println("遍历ArrayList:");
for (int i = 0; i < arrayList.size(); i++)
{
//取出ArrayList中的数据
if (i != arrayList.size() - 1)
{
System.out.print(arrayList.get(i) + ",");
} else
{
System.out.print(arrayList.get(i));
}
}
}
}
结果:
遍历ArrayList:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
Process finished with exit code 0
以上实例包含ArrayList的创建,数据的添加,以及从ArrayList中读取数据。
ArrayList常用的API有如下:
Modifier and Type | Method and Description |
---|---|
boolean | add(E e) 将指定的元素追加到此列表的末尾。 |
void | add(int index, E element) 在此列表中的指定位置插入指定的元素。 |
boolean | addAll(Collection<? extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。 |
boolean | addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。 |
void | clear() 从列表中删除所有元素。 |
E | get(int index) 返回此列表中指定位置的元素。 |
boolean | isEmpty() 如果此列表不包含元素,则返回 true 。 |
E | remove(int index) 删除该列表中指定位置的元素。 |
boolean | remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。 |
int | size() 返回此列表中的元素数。 |
3. 源码分析
3.1 构造方法
ArrayList有三个构造方法:
第一个构造方法:无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在此方法中, elementData 是一个Object类型的数组,并且对其赋值一个空的Object类型数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
第二个构造方法:不常用。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
此构造方法中, 先将传入的参数(集合)转化为数组,并赋值给 elementData 。当 elementData 长度不为空时,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去做转化。当 elementData 长度为空时,给 elementData 赋值一个空的Object类型的数组:
private static final Object[] EMPTY_ELEMENTDATA = {};
第三个构造方法:
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);
}
}
当传入的参数 initialCapacity 大于0,则给 elementData 赋一个大小为 initialCapacity 的 Object 类型数组。当 initialCapacity 等于0,则给 elementData 赋一个Object类型的空数组。当 initialCapacity 小于0 ,则直接抛出一个异常。
3.2 add 方法
在ArrayList中增加元素的时候,会使用add
函数,总共有4种方法:
这里先分析 add(E e) :
此方法直接添加数据元素到arraylist的尾部。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在此方法中,先调用 ensureCapacityInternal(size + 1),把当前列表的长度加1后当做参数 minCapacity,传入 ensureCapacityInternal() 方法中:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
在此方法中,首先将 minCapacity 和 elementData 作为参数传入方法 calculateCapacity() 中:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
此方法主要的功能是:
如果 elementData 是空数组,则返回 minCapacity 和 DEFAULT_CAPACITY(10) 中的最大值。如果 elementData不是空数组,则返回 minCapacity,并当做参数传入 ensureExplicitCapacity() 方法中:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
首先 modCount 增加1,然后,当传入的值比数组 elementData 的长度还长的时候,执行方法 grow(minCapacity):
private void grow(int minCapacity) {
// 将elementData数组的长度作为原容量
int oldCapacity = elementData.length;
// 扩容为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 若1.5倍扩容后还不够,则将最小容量作为新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 限制最大容量
newCapacity = hugeCapacity(minCapacity);
// 进行原有数据元素copy处理
elementData = Arrays.copyOf(elementData, newCapacity);
}
在grow() 方法中,首先把数组 elementData 的长度当做原容量,并对其进行扩容1.5倍作为新容量。如果扩容1.5倍后的容量还不够,则将新容量的值更改为传入的最小容量(minCapacity)。接着判断通过调用方法 hugeCapacity() 限制新容量的最大值。最后,通过调用 Arrays.copyOf 实现对数组 elementData 的扩容。Arrays.copyOf 在这里就进行介绍,想了解相关的知识,请移步此篇blog:System.arrayCopy()与Arrays.copyOf()的区别。
继续回到 add(E e) 方法中,以上的步骤实现了对数组 elementData 的扩容后,接着调用了 elementData[size++] = e ,对数组进行了赋值操作,并返回 true,表示数据添加成功。
接下来继续分析add(int index, E element):
此方法的作用是将元素插入到ArrayList中的指定位置。
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
在这个方法中,首先先判断要插入的位置index 是否超出ArrayList的长度,以及是否小于0,如果是,则抛出数组下标越界的异常(IndexOutOfBoundsException)。接着调用方法 ensureCapacityInternal(size + 1),对内部数组 elementData 进行扩容操作。当数组扩容结束后,调用 System.arraycopy() 将指定位置 index 后的元素后移一位。想了解 System.arraycopy() 的相关知识,请移步此篇blog:System.arrayCopy()与Arrays.copyOf()的区别。最后对指定位置index的元素进行赋值操作并将size自增1。
3.3 remove 方法
在ArrayList中,删除单个元素有以下两种方法,下面将分别介绍:
remove(int index) :
用于删除该列表中指定位置的元素。
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) 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的长度,如果超过,则抛出数组下标越界的异常(IndexOutOfBoundsException);接着读取指定下标位置的数据,并赋值给 oldValue ;然后计算需要移动的数据个数 numMoved ,当需要移动的个数大于0的时候,调用方法System.arraycopy(),这样指定下标后的元素都会往前移动一位;最后将代表ArrayList大小的size值减一,并将数组elementData 的最后一位置为null,并且返回删除的数据。
remove(Object o):
用于从列表中删除第一个出现的指定元素(如果存在)。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
当调用此方法的时候,会把需要删除的 Object 类型的数据 o 分为空和非空进行处理。然后对数组 elementData 进行遍历处理,当 o 为null 的时候,如果遍历到数组 elementData 中第一个数据为 null,则找到对应的下标;当 o 不为 null 的时候,如果遍历到数组 elementData 中第一个数据为 o,则找到对应的下边。接着调用 fastRemove(index),把下标index当做参数传入方法 fastRemove()。而方法 fastRemove(int index) 和 remove(int index) 的逻辑基本相同。
3.4 get 方法
get 方法只有一种,用于返回此列表中指定位置的元素。
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
首先进行越界判断,接着直接返回数组 elementData 中下标为 index 的数据元素。
4. 总结
ArrayList本质上就是一个 Object 类型的数组,能存放null值。ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍。由于本质是个数组,所以随机存取快,但是在增删时候,需要数组的拷贝复制,会比较耗费性能。从以上的add和remove的分析,可以看出ArrayList不是线程安全的,如果需要考虑线程的安全性,推荐用Vector,内部代码实现大部分都是一样的,只不过vector的方法都加上了synchronized锁来保证线程安全。