介绍
ArrayList是我们日常开发中最常用的集合类。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。
属性
//默认容量大小
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认无设置容量时空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放元素数组
transient Object[] elementData;
//数组真实大小
private int size;
构造方法
1、带初始容量大小的构造
public ArrayList(int initialCapacity) {
//初始容量大于0,构造此容量大小的数组
//等于0,构造空数组
//小于0,抛异常
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2、无参构造
public ArrayList() {
//构造默认无设置容量时空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3、传入集合的有参构造
public ArrayList(Collection<? extends E> c) {
//几个转数组
elementData = c.toArray();
//判断数组长度不等于0,判断elementData类型是否为Object[]
//等于0则用空数组替换
if ((size = elementData.length) != 0) {
//类型不为Object[],重新拷贝成Object[].class类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加元素
1、add (e)添加元素至数组最后
public boolean add(E e) {
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//设置数组最后一位为e
elementData[size++] = e;
return true;
}
//检查是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
//数组为默认无设置容量时空数组,则
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//最小容量为默认容量10和传入的最小容量的大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保有明确容量,即有需要则扩容
ensureExplicitCapacity(minCapacity);
}
//确保有明确容量,即有需要则扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//需要的最小容量大于数组的长度,则扩容操作
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
//原始数组的容量
int oldCapacity = elementData.length;
//扩容为原始数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新数组容量小于需要的最小容量,则以最小容量为准
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新数组容量已经超过最大容量,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//拷贝老数组至新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
2、add(i,e)添加原始至数组的指定位置
public void add(int index, E element) {
//范围检查,是否在数组范围内
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//数组下标后面的元素往后移动一格位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//设置指定下标的元素为element
elementData[index] = element;
//数组大小+1
size++;
}
private void rangeCheckForAdd(int index) {
//如果下标大于数组大小或者小于0,则抛异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3、addAll(collection)添加集合至数组
public boolean addAll(Collection<? extends E> c) {
//集合转数组
Object[] a = c.toArray();
int numNew = a.length;
//检查是否需要扩容
ensureCapacityInternal(size + numNew);
//将集合所有信息拷贝至数组的尾部
System.arraycopy(a, 0, elementData, size, numNew);
//数组大小加集合大小
size += numNew;
return numNew != 0;
}
4、addAll(i,collection)添加集合至数组指定下标位置
public boolean addAll(int index, Collection<? extends E> c) {
//数组范围检查
rangeCheckForAdd(index);
//集合转数组
Object[] a = c.toArray();
int numNew = a.length;
//检查是否需要扩容
ensureCapacityInternal(size + numNew);
//计算移动数量,集合大小减去指定下标
int numMoved = size - index;
//移动数量大于0,则将此index位置之后的元素往后挪位,空出位置
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
//将集合移动到index下标位置
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
设置元素/获取元素
set设置元素
public E set(int index, E element) {
//检查下标是否在范围内
rangeCheck(index);
//取出数组对应下标元素最终返回
E oldValue = elementData(index);
//设置数组下标的元素为新element
elementData[index] = element;
return oldValue;
}
get获取
public E get(int index) {
//检查下标是否在范围内
rangeCheck(index);
//返回数组下标对应元素
return elementData(index);
}
移除元素
1、remove(i)根据下标移除
public E remove(int index) {
//检查下标是否在范围内
rangeCheck(index);
modCount++;
//获取下标数组值
E oldValue = elementData(index);
//计算移动数量
int numMoved = size - index - 1;
//移动数量大于0,则将index后的元素往前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//数组最后一位设为null
elementData[--size] = null;
//返回移除位置原始值
return oldValue;
}
2、remove(o)根据元素移除
public boolean remove(Object o) {
//元素为null
if (o == null) {
//数组从头循环
for (int index = 0; index < size; index++)
//找出第一个元素为null的下标,移除元素
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//元素不为null,从数组头部循环,根据equals找出元素下标
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
//移除元素
fastRemove(index);
return true;
}
}
return false;
}
//根据下标快速移除元素
private void fastRemove(int index) {
modCount++;
//计算移动数量
int numMoved = size - index - 1;
//移动数量大于0,则将index后的元素往前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//数组最后一位设为null
elementData[--size] = null;
}
交集
public boolean retainAll(Collection<?> c) {
//集合c要求非空
Objects.requireNonNull(c);
return batchRemove(c, true);
}
//批量移除
//complement为true表示删除c中不包含的元素
//complement为false表示删除c中包含的元素
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// 使用读写两个指针同时遍历数组
// 读指针每次自增1,写指针放入元素的时候才加1
int r = 0, w = 0;
boolean modified = false;
try {
//循环数组
for (; r < size; r++)
//判断传入集合c是否包含元素
if (c.contains(elementData[r]) == complement)
//符合条件则写入elementData,w+1
elementData[w++] = elementData[r];
} finally {
//一般情况下r肯定会等于size,除非contains抛异常
//若异常则把未读的元素都拷贝到写指针之后
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
//写指针不等于数组大小
if (w != size) {
//将写指针之后的数据设为null,方便GC
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
总结
1、ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,不会进行缩容
2、ArrayList支持随机访问,通过索引访问元素极快
3、ArrayList添加元素到中间或者从中间删除元素比较慢,因为要移动后面元素