[Java 集合] ArrayList源码分析
源码基于JDK 11
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable{ }
一、属性
private static final long serialVersionUID = 8683452581122892189L;
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
//默认的空数组,创建集合时使用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
//存放数据的数组
transient Object[] elementData;
//集合中元素的个数
private int size;
//最大容量
private static final int MAX_ARRAY_SIZE = 2147483639;
分析
- DEFAULT_CAPACITY 默认容量,我在源码中没有看到使用这个常量。不过代码中,通过空参的构造方法创建集合时,数组的初始长度是0,当第一次调用add方法时,会创建一个长度为10的数组(下面会结合源码分析)。
- EMPTY_ELEMENTDATA 空数组,通过new ArrayList(0)创建时用的是这个空数组。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA 通过new ArrayList()创建时用的是这个空数组。
与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为10个元素。 - elementData 真正存放元素的地方,使用transient是为了不序列化这个字段。
- size 存储元素的个数,而不是elementData数组的长度。
二、构造方法
ArrayList()
初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会在添加第一个元素的时候扩容为默认的大小,即10。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList(int initialCapacity)
传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else {
if (initialCapacity != 0) {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
// 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList(Collection<? extends E> c)
传入集合并初始化elementData,这里会使用拷贝把传入集合的元素拷贝到elementData数组中,如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。
public ArrayList(Collection<? extends E> c) {
//集合转数组
this.elementData = c.toArray();
if ((this.size = this.elementData.length) != 0) {
//类型检查,如果c.toArray()不是Object[]类型,那么就重新拷贝
if (this.elementData.getClass() != Object[].class) {
this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
}
} else {
//传入集合的元素个数为0,初始化为EMPTY_ELEMENTDATA空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
三、常用成员方法
add(E e)
添加元素到末尾
public boolean add(E e) {
//modCount是继承自AbstractList类,初始为0
++this.modCount;
//调用私有的add方法,将要添加的元素,当前的数组,现有元素个数传入
this.add(e, this.elementData, this.size);
return true;
}
private void add(E e, Object[] elementData, int s) {
//如果当前数组长度和元素个数相等(也就是数组满了),那么就要先扩容了
if (s == elementData.length) {
//数组扩容
elementData = this.grow();
}
//在数组尾部插入数据
elementData[s] = e;
this.size = s + 1;
}
private Object[] grow() {
//设置最小容量为size+1
return this.grow(this.size + 1);
}
private Object[] grow(int minCapacity) {
// 以新容量拷贝出来一个新数组
return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
//原来的容量,也就是当前数组的长度
int oldCapacity = this.elementData.length;
//新容量,为原来容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果newCapacity <= minCapacity ,则考虑minCapacity
if (newCapacity - minCapacity <= 0) {
//如果当前数组是个空数组,需要判断minCapacity和DEFAULT_CAPACITY的大小(这里并没有使用常量)
if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(10, minCapacity);
} else if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity;
}
} else {
//如果newCapacity > minCapacity ,还要比较newCapacity 和MAX_ARRAY_SIZE的大小(这里也没有使用常量)
return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
}
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
//如果minCapacity > MAX_ARRAY_SIZE则取Integer.MAX_VALUE
return minCapacity > 2147483639 ? 2147483647 : 2147483639;
}
}
- 首先modCount自增
- 判断 元素的个数 与 数组的长度 是否相等(只考虑相等情况)
- 将(现有元素个数+1)作为 minCapacity,对数组进行扩容
- 将当前数组长度作为oldCapacity,将oldCapacity的1.5倍作为newCapacity
- 比较newCapacity和minCapacity的大小
- newCapacity<= minCapacity
- 当前数组是空数组,新数组的长度取minCapacity和DEFAULT_CAPACITY中较大的
- minCapacity,抛出异常
- 新数组的长度取minCapacity
- newCapacity > minCapacity ,还要比较newCapacity 和MAX_ARRAY_SIZE的大小
- newCapacity <= MAX_ARRAY_SIZE,新数组的长度取newCapacity
- newCapacity > MAX_ARRAY_SIZE
- minCapacity < 0 ,抛出异常
- 如果minCapacity > MAX_ARRAY_SIZE则取Integer.MAX_VALUE
- 如果minCapacity < MAX_ARRAY_SIZE则取minCapacity
- newCapacity<= minCapacity
- 调用Arrays.copyOf(T[] original, int newLength)方法,将原来的数据拷贝到新数组中
- 在数组的尾部插入数据
- 元素个数自增
add(int index, E element)
添加元素到指定位置
public void add(int index, E element) {
//检查索引是否越界
this.rangeCheckForAdd(index);
++this.modCount;
int s;
Object[] elementData;
//如果当前元素个数=数组长度,需要扩容
if ((s = this.size) == (elementData = this.elementData).length) {
//扩容逻辑同上(add(E e))
elementData = this.grow();
}
//将index及其之后的元素往后挪一位,则index位置处就空出来了
System.arraycopy(elementData, index, elementData, index + 1, s - index);
//将新元素插入到index索引位置
elementData[index] = element;
this.size = s + 1;
}
private void rangeCheckForAdd(int index) {
if (index > this.size || index < 0) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(index));
}
}
- 检查索引是否越界;
- 检查是否需要扩容;
- 将索引位置及之后的元素都往后挪一位;
- 在索引位置插入的元素;
- 数组元素个数加1;
addAll(Collection<? extends E> c)
将新集合的元素添加到原集合的后面
有很多文章把这种行为成为取两个集合的并集,这个说法是不对的。
例如:集合A[1,2,3],集合B[2,3,4]
那么通过addAll方法的到的结果是[1,2,3,2,3,4]
而A和B的并集结果是[1,2,3,4]
public boolean addAll(Collection<? extends E> c) {
//将集合转成数组
Object[] a = c.toArray();
++this.modCount;
//数组a的长度,也是集合c中元素的个数
int numNew = a.length;
if (numNew == 0) {
return false;
} else {
Object[] elementData;
int s;
//如果数组a的长度超过了数组elementData中的空余容量(装不下),那么就要扩容
if (numNew > (elementData = this.elementData).length - (s = this.size)) {
//扩容时,minCapacity = 两个集合中元素个数的总和
elementData = this.grow(s + numNew);
}
// 将数组a中元素全部拷贝到数组elementData的最后
System.arraycopy(a, 0, elementData, s, numNew);
this.size = s + numNew;
return true;
}
}
- 集合c转成数组a
- 检查数组a是否为空
- 检查是否需要扩容
- 把数组a中的元素拷贝到elementData的尾部;
remove(int index)
删除指定索引位置的元素
public E remove(int index) {
//检查索引是否越界
Objects.checkIndex(index, this.size);
//数组elementData地址给到es
Object[] es = this.elementData;
//获取数组中索引为index位置的元素
E oldValue = es[index];
this.fastRemove(es, index);
//返回被删除的元素
return oldValue;
}
private void fastRemove(Object[] es, int i) {
++this.modCount;
int newSize;
//判断删除的是否是末尾的元素
if ((newSize = this.size - 1) > i) {
//不是末尾元素,将数组es中索引(i+1)及之后的元素,向前移动一位
System.arraycopy(es, i + 1, es, i, newSize - i);
}
//将数组中最后一个元素置为null
es[this.size = newSize] = null;
}
- 检查索引是否越界;
- 获取指定索引位置的元素;
- 如果删除的不是最后一位,则其它元素往前移一位;
- 将最后一位置为null,方便GC回收;
- 返回删除的元素。
可以看到,ArrayList删除元素的时候并没有缩容。
remove(Object o)
删除指定元素
public boolean remove(Object o) {
Object[] es = this.elementData;
int size = this.size;
int i = 0;
//检查删除的元素是否是null
if (o == null) {
//需要删除null时,先便利数组,然后尝试找到第一个null所在位置的索引i
while(true) {
if (i >= size) {
return false;
}
if (es[i] == null) {
break;
}
++i;
}
} else {
//遍历整个数组,找到元素第一次出现的位置
while(true) {
if (i >= size) {
return false;
}
if (o.equals(es[i])) {
break;
}
++i;
}
}
//删除数组中索引为i的元素
this.fastRemove(es, i);
return true;
}
- 找到指定元素第一次出现的位置,如果未找到,返回false
- 快速删除;
set(int index, E element)
修改指定索引位置的元素为指定元素
public E set(int index, E element) {
//检查索引是否越界
Objects.checkIndex(index, this.size);
//记录要被修改的元素
E oldValue = this.elementData(index);
//将index位置的值设置成指定元素
this.elementData[index] = element;
//返回被删除的元素
return oldValue;
}
- 检查索引是否越界;
- 记录将要被修改的元素;
- 将指定元素放置在指定索引位置;
- 返回被删除的元素;
get(int index)
获取指定索引位置的元素
public E get(int index) {
//检查索引是否越界
Objects.checkIndex(index, this.size);
//返回数组中索引index位置的元素
return this.elementData(index);
}
E elementData(int index) {
return this.elementData[index];
}
- 检查索引是否越界
- 返回索引位置处的元素