首先我们来查看Arraylist的底层源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
可以看到ArrayList继承于AbstractList类
且其有3个标记接口,分别是:
RandomAccess标识其支持快速随机访问;
Cloneable标识其支持对象复制;
Serializable标识其可序列化;
**
ArrayList不是线程安全的,只能用在单线程环境下
1.ArrayList的构造方法
一个是无参构造,另一个是有参构造(需要传入初始容量值),底层实现如下:**
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
//默认初始容量为10
/**
*ArrayList实例共享的一个空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
数组列表元素存储在其中的数组缓冲区。数组列表的容量是这个数组缓冲区的长度。当添加第一个元素时,任何ElEMENTDATA = = DEFAULT CAPACITY _ EMPTY _ ElEMENTDATA的空数组列表都将扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData; // non-private to simplify nested class access
private int size;
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList的两个构造方法的目的都是初始化底层数组 elementData。区别在于无参构造方法会将 elementData 初始化一个空数组,当插入第一个元素之后,会默认数组数组容量为10;而有参构造方法会将 elementData 初始化为传入参数值大小(>= 0)的数组。一般情况下,我们使用默认的构造方法;如果知道待插入元素的数量,采用有参构造方法,按需分配,避免浪费
2.元素的插入
对于数组的插入,情况分为两种:1.在元素序列的尾部插入 2.在元素序列的其他位置插入,底层实现如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
elementData[size++] = e;加入新元素e,size加1
return true;
}
/**
在元素序列 index 位置处插入
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
// 将新元素插入至 index 处
size++;
}
由上可知,当在尾部插入元素时,1. 检查数组的空间是否足够;2. 将元素插入数组的尾部;当插入元素在指定位置index处时,1. 首先检查数组的空间是否足够; 2. 将数组中下标位于index之后的元素,集体向后挪一位; 3. 将元素插在index位置(此种插入方式不推荐,因为其时间复杂度为O(n),在插入、删除操作频繁的时候,更推荐链表)
3.扩容
/*
扩大容量,以确保它至少可以容纳由最小容量参数指定的元素数量。
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;获取数组大小(即数组的容量)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)//如果扩容后的新容量还是没有传入的所需的最小容量大
newCapacity = minCapacity;//将数组容量设为最小容量
if (newCapacity - MAX_ARRAY_SIZE > 0)//若新容量比数组最大容量还大,执行扩容,新数组将十分大,因为数组最大容量为10亿
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
//将就数组中的数,拷贝到新数组中
}
我们对数组进行扩容时,扩容后新数组的大小为旧数组大小的1.5倍,旧数组将废弃,故尽量避免此种扩容方式,因为当元素数量巨大的时候,会产生垃圾,造成极大内存浪费。
4.元素的删除
/*
移除列表中指定位置的元素。向左移动任何后续元素(从它们的索引中减去一个)。
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/*从列表中移除指定元素的第一次出现(如果存在)。如果列表不包含该元素,它将保持不变
*/
public boolean remove(Object o) {
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])) {
fastRemove(index);
return true;
}
}
return false;
}
当在ArrayList中删除指定元素时,找到指定元素,将元素删除之后,将index后的元素整体向前移动,将空出来的位置置为空,方便GC回收。
5.遍历ArrayList
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 返回列表中元素的列表迭代器(按正确的顺序)
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
*以正确的顺序返回列表中元素的迭代器。
*/
public Iterator<E> iterator() {
return new Itr();//返回一个内部类对象
}
private class Itr implements Iterator<E> {
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; //返回的最后一个元素的索引;-1(如果没有返回)
int expectedModCount = modCount;//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作
Itr() {}
//检测对象数组是否还有元素
public boolean hasNext() {
return cursor != size;//如果cursor==size,说明已经遍历完了
}
ArrayList的元素遍历的方式采用 Iterator迭代器
不足之处,欢迎补充。