ArrayList源码解析
1.ArrayList介绍
- ArrayList的继承关系
ArrayList是List接口的主要实现类,线程不安全,效率高,底层使用Object[] elementData存储
2.ArrayList分析
2.1 属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* RandomAccess:标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的list中表现的更 加高效
* Cloneable:一个标记接口,只有实现这个接口后,类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
* Serializable是启用其序列化功能的接口。支持对象序列化,进行对象传输
*/
//初始容量
private static final int DEFAULT_CAPACITY = 10;
//空集合,注意不是null,是表示没有数据
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认数组,当使用无参构造函数初始化时会赋值给elementData
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数据的数组
transient Object[] elementData;
//elementData中元素个数
private int size;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
2.2 构造函数:
ArrayList有三个构造函数: 无参 、参数-int、参数-collection
ArrayList a1 = new ArrayList<>();
ArrayList a2 = new ArrayList<>(12);
ArrayList a2 = new ArrayList<>(Arrays.asList(12,13,45));
- 无参构造函数
public ArrayList() {
//ArrayList无参构造函数将elementData赋值为{}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 有参构造函数-参数为int
//创建一个指定大小的ArrayList
public ArrayList(int initialCapacity) {
//当传入的参数大于0,则创建指定容量大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//当传入的参数等于0,则将elementData赋值为{}
this.elementData = EMPTY_ELEMENTDATA;
} else {
//当传入的参数小于0,则跑出非法参数异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 有参构造函数-参数为Collection
//创建一个与指定集合元素一样的Arraylist
public ArrayList(Collection<? extends E> c) {
//当传入参数是一个集合时,首先调用toArray()将集合类中的数组赋值给elementData
elementData = c.toArray();
/**更新size为elementData.length,并判断如果size!=0,继续判断
-如果toArray()返回的数组类型不是Object[]类型时,会调用Arrays.copyOf()将原数组拷贝到新数组中去,而且类型还定义为Object类;
-如果size=0,即传入的为空集合,则将elementData赋值为空
**/
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
public Object[] toArray() {//数组拷贝
return Arrays.copyOf(elementData, size);
//Arrays.copyOf(Object[] o,int length);
//实现数组的复制,并返回复制后的数组。参数是被复制的数组和复制的长度
}
2.3 常用方法:
1) add(E e)
//添加元素
public boolean add(E e) {
//当前所需实际容量为size+1,通过ensureCapacityInternal确定elementData数组的容量是否充足,不够则扩容
ensureCapacityInternal(size + 1);
//再将数据放入数组的size++位置上
elementData[size++] = e;
return true;
}
- ensureCapacityInternal():主要找到实际需要的最小容量,并传入ensureExplicitCapacity()中判断扩容与否
private void ensureCapacityInternal(int minCapacity) {
//首先验证属性elementData是否等于默认的初始化数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果等于则表明集合刚被初始化,那么形参minCapacity赋值为10(DEFAULT_CAPACITY)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//拿到实际需要的最小容量后,通过ensureExplicitCapacity()验证minCapacity是否大于elementData的length,大于才扩容;
ensureExplicitCapacity(minCapacity);
}
- ensureExplicitCapacity():拿实际所需最小容量minCapacity验证elementData长度是否够,不够则用grow(minCapacity)扩容
private void ensureExplicitCapacity(int minCapacity) {
//modCount变量是从AbstractList继承下来的,用于记录对ArrayList对象操作的次数
modCount++;
//如果形参minCapacity的数值比当前的elementData数大,则调用grow()扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- grow(int minCapacity):elementData数组扩容,取传递进来的minCapacity与newCapacity的最大值作为真正的下一个elementData的长度
private void grow(int minCapacity) {
//elementData原始长度
int oldCapacity = elementData.length;
//先在原数组基础上计算扩容后的数组长度:newCapacity=当前数组长度*1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的数组长度newCapacity<实际所需最小容量minCapacity,则将扩容长度赋值为实际所需的minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果扩容的数组长度>数组最大容量,则用hugeCapacity()进行容量的判断,将其更新为int的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//利用Arrays.copyOf()把elementData复制到新数组中并赋值给elementData;
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果minCapacity<0,则报栈溢出错误
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果minCapacity大于最大数组容量,则返回数组扩容容量为Integer.MAX_VALUE,否则为 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
扩容总结:调用Arraylist无参构造时,底层Object[] elementData初始化为{},当第一次add时,先确定数组容量是否充足,通过ensureCapacityInternal(size+1)将当前需要的最小容量计算出来【如果elementData等于默认的初始化数组,则表明集合刚被初始化,则minCapacity为10】,再通过ensureExplicitCapacity()形参minCapacity与elementData长度相比较是否够,不够则用grow(minCapacity)扩容,扩容规则为数组当前的容量+(数组当前的容量/2),取传递进来的minCapacity与newCapacity的最大值作为真正的下一个elementData的长度,如果扩容的长度大于elementData最大容量,则用hugeCapicity()进行处理,如果minCapacity大于最大数组容量,则返回数组扩容容量为Integer.MAX_VALUE,否则为 MAX_ARRAY_SIZE。
2) remove (int index)
//移除指定位置的元素
public E remove(int index) {
//检查下标是否非法或越界
rangeCheck(index);
modCount++;
//获取指定位置的元素
E oldValue = elementData(index);
//numMoved需要移除的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//如果需要移除的元素的个数>0,则将元素往前移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size位置元素置空并将size--
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
* 参数:
* Object src : 原数组
* int srcPos : 从元数据的起始位置开始
* Object dest : 目标数组
* int destPos : 目标数组的开始起始位置
* int length : 要copy的数组的长度
*
*/
//检查形参索引是否不合法
private void rangeCheckForAdd(int index) {
//如果形参index < 0 或大于数组长度,则报下标越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3) remove(Object o)
public boolean remove(Object o) {//移除指定的元素
//如果要移除的元素为空,则遍历整个数组找到为空的元素的下标,用fastRemove方法移除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {//如果不为空调用对象的equals()挨个与elemetData中的元素比较,如果相等则移除
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,则将elementData数组中index+1之后的数字都往前移动一个单位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size位置元素置空并将size--
elementData[--size] = null; // clear to let GC do its work
}
4) set(int index, E element)
public E set(int index, E element) {//修改指定位置元素
//检查下标是否非法或越界
rangeCheck(index);
//获取指定位置的值并更新该位置的值,返回旧值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
5) get(int index)
public E get(int index) {//获取指定位置的元素
//检查下标是否非法或越界
rangeCheck(index);
return elementData(index);
}
6)add(int index, E element)
public void add(int index, E element) {//在指定位置插入元素
//检查插入的索引是否越界或非法
rangeCheckForAdd(index);
//确定elementdata容量是否充足,不充足则扩容
ensureCapacityInternal(size + 1);
//将elementData从index位置之后的元素往后移动一个位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
7)size()
public int size() {//获取集合元素个数
return size;
}
8)ArrayList的遍历
-
方式一:iterator迭代器
Collection接口继承了java.lang.iterator,该接口有一个iterator()方法。所以实现了Collection接口的集合类都有一个iterator
()方法,用以返回实现了iterator接口的对象,默认游标在第一个元素之前
iterator仅用于遍历集合,iterator本身不提供承装对象的能力。如果需要创建iterator对象,则必须有一个被迭代的集合
内部方法:
- hashNext();判断是否有下一个元素
- next()将指针下移,并返回当前元素
- remove(),删除迭代器指定的元素
方式二:增强for
方式三:for(;;😉
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
方式1:
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
方式2:
for(Object obj : coll){
System.out.println(obj);
}
}
2.4 其他方法
boolean addAll(int index,Collection coll):从指定位置index开始将coll中元素添加进来
int indexOf(Object obj);返回obj在集合中首次出现的位置
int lastIndexOf(Object obj);返回obj在集合中最后一次出现的位置
List subList(int fromIndex,int toIndex);返回从fromIndex开始到toIndex的子集合