概述:ArrayList是一个可以动态增长的索引序列,底层是数组。
源码分析:
继承:
AbstractList抽象类
该类实现了List<E>接口一些通用方法,其子类可以得到这些通用方法,实现自己特有的方法,使得代码更简洁,减少重复代码。
实现:
List接口,为了设计。
RandomAccess接口,一个标记性接口,用来快速随机存取,提高普通for循环遍历的性能。
Cloneable接口,一个克隆接口,实现它就可以使用object.clone()方法。
Serializable接口,一个序列化接口,可以将类转化成字节流传输。
特点:
1. 可以存放null值。
2. 底层是一个elementData数组。
3. grow()方法,ArrayList的核心方法,可以通过该方法进行扩容。
4. 查询快,但是增删慢。
属性:
// 版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省容量
private static final int DEFAULT_CAPACITY = 10;
// 空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省空对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组
transient Object[] elementData;
// 实际元素大小,默认为0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法:
作用:初始化一个默认10的容量(也可以自定义容量)的 elementData 数组。
重点:在构造方法中,只是定义容量大小,还没真正实现!(在grow方法实现)
1. 无参构造:
/**
* 默认10的容量
*/
public ArrayList() { //elementData其实就是底层数组
//这里赋给一个空数组,容量在程序调用add()方法时,进行默认10的容量初始化。
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 有参构造:
/**
* 指定容量的初始化构造函数
* initialCapacity:指定数组容量大小
*/
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);
}
}
/**
* 将指定集合添加到ArrayList集合中
*/
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;
}
}
add方法:
1.boolean add(E e) //添加元素,默认在末尾添加
2.void add(int index, E element) //在指定位置添加元素
3.boolean addAll(Collection<? extends E> c) //添加一个集合元素进本集合
4.boolean addAll(int index, Collection<? extends E> c)//添加一个集合元素进本集合的特定位置
1. add(E e)
/**
* 在数组的末尾添加元素
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断加入元素后的总数量有没有超过数组容量
elementData[size++] = e; //添加元素e进数组,然后 size+1
return true;
}
下面是具体方法实现:
/**
* 先判断底层数组是否为空数组,如果为空:minCapacity=10。如果不为空:minCapacity原样输出
* minCapacity:数组的容量
* elementData:数组
*/
private void ensureCapacityInternal(int minCapacity) {
// 如果 elementData 是空数组,无参构造
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 赋值,默认为容量为10,除非minCapacity更大。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* 判断加入元素后的总数量有没有超过数组容量
*
* minCapacity:数组的容量
* elementData:数组
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改次数增加
//一、elementDate 为空数组,minCapacity为10,10-0=10>0,进行第一次扩容,也即是数组容量初始化。
//二、elementDate 不为空数组,判断此时的元素总数是否大于数组的容量。
if (minCapacity - elementData.length > 0)
grow(minCapacity); //扩容
}
真正扩容和初始化数组容量的地方grow()方法。
/*
* 实现初始化容量和扩容
* */
private void grow(int minCapacity) {
// oldCapacity 数组大小,如果是无参构造的空数组,大小为0,minCapacity为10
int oldCapacity = elementData.length;
//扩容为原来oldCapacity 的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) //1.5倍不够,或者elementData为空数组(0-10=-10)
newCapacity = minCapacity; //要多少是什么(空数组:初始化容量为10)
if (newCapacity - MAX_ARRAY_SIZE > 0) //容量是否超过原本限制的最大容量MAX_ARRAY_SIZE
newCapacity = hugeCapacity(minCapacity); // 最多能给多少是多少
// 方法参数:elementData:要赋值的数组,newCapacity:复制后的容量大小
elementData = Arrays.copyOf(elementData, newCapacity); //搬迁数组。
}
/*
* 当新扩容的容量(1.5倍)大于 MAX_ARRAY_SIZE时,新容量的取值。
* 返回能够取的最大值
* */
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 将未扩容的容量与 MAX_ARRAY_SIZE 比较
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2.add(int index, E element)
/*
* 在指定索引位置添加元素
* index:指定索引
* element:要添加的元素
* */
public void add(int index, E element) {
rangeCheckForAdd(index); //检查索引是否越界。
ensureCapacityInternal(size + 1); //判断加入元素后的总数量有没有超过数组容量。
System.arraycopy(elementData, index, elementData, index + 1, //将elementData在插入位置后的所有元素往后面移一位。
size - index); // index为要移动前的起始位, index+1为移动后的起始位,size-index为移动数量。
elementData[index] = element; //添加元素。
size++;
}
/*
* 判断输入的 index 有没有越界
* */
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) //判断index有没有越界。
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法:
1. E remove(int index):删除并返回指定索引的值
2. boolean remove(Object o):删除该值
3. void clear():清理所有数据
4. boolean removeAll(Collection<?> c) :批量删除
1. E remove(int index);
/*
* 在指定位置移除元素
* */
public E remove(int index) {
rangeCheck(index); //判断是否越界
modCount++; //修改次数增加
E oldValue = elementData(index); //取出该值
// 删除一个索引的元素,后面的元素都要往前移一位。
int numMoved = size - index - 1; //numMoved为移动的元素数量
if (numMoved > 0) //(size-1) - index,判断 index在[0,size-1)内,如果不是最后一个元素,不用移动数组。
//元素移动
//elementData:选择数组。
//index+1:选择数组中要复制元素的起始位。
//elementData:目标数组。
//index:目标数组要存放加进来元素的起始位。
//numMoved:移动的元素数量。
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; //清理原来 index=size-1的元素,size-1
return oldValue;
}
2. boolean remove(Object o);
/*
* 移除指定元素
* o:要删除的元素
* */
public boolean remove(Object o) {
// 如果要删除空值:使用 == 比较内容,否则使用equals
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])) { //上面排除的null值,可以使用equals方法,防止空指针异常。
fastRemove(index);
return true;
}
}
return false;
}
3. void clear():
public void clear() { //所有值赋null,方便回收
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
4. boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false); // 调用batchRemove ,批量删除c与elementData共同的元素
}
public boolean retainAll(Collection<?> c) { //也调用 batchRemove,求交集
Objects.requireNonNull(c);
return batchRemove(c, true);
}
//batchRemove
private boolean batchRemove(Collection<?> c, boolean complement) { //complement : true:求交集 flase:删除
final Object[] elementData = this.elementData;
int r = 0, w = 0; //r控制循环,w为交集个数
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) //如果有交集,集合从index=0开始覆盖元素,添加与集合c有交集的元素
elementData[w++] = elementData[r]; //如果是批量删除,集合从index=0开始覆盖元素,添加与集合c无交集的元素
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) { //contains异常处理
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
set方法
public E set(int index, E element) { //在指定位置置换元素
rangeCheck(index); //判断 index 是否越界
E oldValue = elementData(index);
elementData[index] = element; //替换元素
return oldValue;
}
get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
总结:
一、初始化容量数组步骤:
1. 构造方法定义容量大小,默认为10。
2. add方法,确认容量的大小。
3. grow方法,真正的实现容量。
二、扩容
grow方法,正常情况下扩容为原来的1.5倍。
三、遍历
由于ArrayList底层是数组,实现了 RandomAccess 接口的list,支持快速随机访问,优先选择普通 for 循环,其次 foreach。