ArrayList源码的个人理解
简介
-
ArrayList底层是数组实现的,所以特性也是查询快增删慢。但和数组不同的是它可以实现动态增长,在知晓数据的大概容量时可使用ensureCapacity方法进行手动扩容。
-
ArrayList继承自AbstractList,实现了List,RandomAccess,Cloneable,Serializable接口
-
ArrayList继承AbstractList,实现LIst,实现了增加,删除,修改,遍历等功能。
-
RandomAccess,Cloneable,Serializable都是标志接口,接口内没有任何内容。其中RandomAccess标志着ArrayList实现了随机访问,Cloneable标志着ArrayList重写了Object类的clone方法实现拷贝,而Serializable则标志ArrayList支持序列化。
核心源码
基本参数
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
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;
/**
* ArrayList包含的元素数量
*/
private int size;
构造方法
// 无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参构造,指定初始容量
public ArrayList(int initialCapacity) {
// 参数的合法性校验
if (initialCapacity > 0) {
// 创建长度为initialCapacity的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 数组为EMPTY_ELEMENTDATA(空数组)
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) {
// 数组的长度不为0
// c.toArray可能返回的不是object类型数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果传入的集合长度为0,替换为空数组.
this.elementData = EMPTY_ELEMENTDATA;
}
}
trimToSize
修剪此ArrayList实例的容量为最合适值,节约空间
public void trimToSize() {
modCount++;
if (size < elementData.length) {
// 下边可能刚看有点迷,可以看我注掉的这个,会直观一些
// elementData = ((size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size));
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
add方法和grow方法(重要)
扩容方法是ArrayList中最核心方法,它最影响性能,所以在知晓数据存储量的情况下,应设置ArrayList的初始容量来避免扩容
public boolean add(E e) {
// 内部容量担保
ensureCapacityInternal(size + 1);
// 将元素放入底层数组的size++位置,就是数组赋值
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);
}
add的重载方法
// index:插入的位置,element:插入的元素
// 慎用,每一次写入都会造成数组的复制和迁移,如果大数据量的情况下非常影响性能
public void add(int index, E element) {
// 索引检查
rangeCheckForAdd(index);
// 内部容量担保
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将elementData数组的index位置及其后的所有元素,插入到elementData数组的inde+1位置
// 其实就是把index位置空出来,把原来index及其后的元素集合向后错一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 将集合填入到ArrayList中
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// 内部容量担保
ensureCapacityInternal(size + numNew); // Increments modCount
// 将集合数组插入到ArrayList的size开始的位置,插入数量为numNew
// 从ArrayList的尾部插入
System.arraycopy(a, 0, elementData, size, numNew);
// size的改变
size += numNew;
return numNew != 0;
}
// 将集合从指定位置填入ArrayList中
// 慎用
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
// 内部容量担保
ensureCapacityInternal(size + numNew); // Increments modCount
// 取出偏移量
int numMoved = size - index;
if (numMoved > 0)
// 进行偏移
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 将新传入的集合从指定位置开始插入
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
总结
ArrayList查询快,增删慢(数组扩容、移位导致),线程不安全
核心是扩容机制
tips:如果知晓需要存入的数据的长度,应在构造时设置好ArrayList的容量,减少扩容提高效率
记得看到过一个面试题,如何让一个数组快速实现元素去重?
把元素数组转为List,然后把List放入Set中就可以快速实现啦
下一篇:LinkedList源码个人笔记
参考:https://snailclimb.gitee.io/javaguide/#/java/collection/ArrayList?id=arraylist简介