ArrayList总结
(1)ArrayList是一个容量能动态增加的动态数组,使用默认构造方法初始化出来的容量是10。
(2)ArrayList 允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个更大的数组。ArrayList扩容的长度是原长度的1.5倍
(3)ArrayList 是非线程安全类。
成员变量和构造函数
继承体系和成员变量
(1)ArrayList 继承于AbstractList,实现了List,表明它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
(2)ArrayList 实现了标志接口RandomAccess,表明它支持快速随机访问。
(3)ArrayList 实现了Cloneable 接口,覆盖了函数 clone(),能被克隆。
(4)ArrayList 实现java.io.Serializable 接口,表明它能通过序列化传输。
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 = {};
//用于默认大小空实例的共享空数组实例。
//我们把它从EMPTY_ELEMENTDATA数组中区分出来,
//以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//保存ArrayList数据的数组
transient Object[] elementData;
//ArrayList 所包含的元素个数
private int size;
//要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建指定容量initialCapacity的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,
也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//如果指定集合元素个数不为0
if ((size = elementData.length) != 0) {
// 考虑到返回的可能不是Object类型的数组,
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size,
Object[].class);
} else {
// 用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
在第三个构造函数中,有一个这样的判断:
if (elementData.getClass() != Object[].class)
考虑到下面一种情况,若没有上面的判断,则会导致elementData实际类型是String[],而不是Object[],因此当你将其中一个元素更换为Object元素时会报错。
List<Object> l = new ArrayList<Object>(Arrays.asList("foo", "bar"));
l.set(0, new Object());
如果没有那句判断,相当于:
Object[] arr = new String[]{"a","b"};
arr[0]=new Object();
add方法
add方法在添加元素前,需要判断是否需要扩容
//将指定元素添加到list的末尾
public boolean add(E e) {
//添加元素后可能导致容量不够,所以需要在添加之前进行判断是否扩容
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
ensureCapacityInternal方法
//minCapacity是当前数组拟添加一个元素的长度,即size + 1
private void ensureCapacityInternal(int minCapacity) {
/*
判断当前数组elementData是否为空元素数组(当使用无参构造方法
时,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)。
若是,则max(默认容量DEFAULT_CAPACITY,size + 1)。
如果是第一次添加元素时,显然minCapacity为10
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity方法
private void ensureExplicitCapacity(int minCapacity) {
/* 问:modCount变量代表了什么?
答:在使用迭代器遍历ArrayList里的数组时,modCount变量用于检查数组里
的元素数量是否发生变化,这主要是在多线程环境中使用:防止一个线程迭代遍历
的同时,另一个线程修改了这个列表的结构。如果modCount变量变化了,
迭代器就抛出异常ConcurrentModificationException。
可以参考如下代码,它会抛出异常。
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==10)
list.remove(integer); //此处
}
当我们remove掉唯一的元素后,size变为了0,而此时Iterator的游标cursor是 1 ,
在ArrayList迭代器的hasNext()方法中:
public boolean hasNext() {
return cursor != size();
}
cursor确实不等于size,因此还会进行下一次循环。如果我们不通过modCount和
expectedModCount(创建迭代器的时候将当时的modCount赋值给expectedModCount)
判断,则程序会报ArrayIndexOutOfBoundsException错误。JDK只抛出使用者造成
的错误,因此定义了检查。
*/
modCount++;
//如果最小容量超出了当前数组长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);//执行扩容的方法
}
grow方法
/*
考虑到不同的JVM会加入一下数据头,当扩容后的容量大于MAX_ARRAY_SIZE,
我们会去比较最小需要容量和MAX_ARRAY_SIZE。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// oldCapacity为旧数组的容量
int oldCapacity = elementData.length;
// newCapacity为新数组的容量,它是旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 检查新容量的大小是否小于最小需要容量,若小于则将最小容量置为数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity方法比较
//最小容量和MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 扩展新数组,然后将原数组中的元素拷贝
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//对minCapacity和MAX_ARRAY_SIZE进行比较:
//若minCapacity大,将Integer.MAX_VALUE作为新数组大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组大小
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
假设当前正添加第二个元素:
(1)在ensureCapacityInternal方法中,调用方法ensureExplicitCapacity(2)
(2)在ensureExplicitCapacity方法中,执行ensureExplicitCapacity(2),此时该方法的唯一条件不满足,不会进入扩容方法grow。
(3)假设之后又添加了第3,4...10个元素,都不会执行扩容方法。
(4)当add第11个元素时,就会进入grow方法:计算出newCapacity = 15,后两个判断条件都不满足,因此数组扩容为15,size变为11.
add(int index,E element)方法
//在元素序列 index 位置处插入
public void add(int index, E element) {
rangeCheckForAdd(index); //校验传递的index参数是不是合法
// 检测是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将 index 及其之后的所有元素都向后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将新元素插入至 index 处
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) //判断index是否在[0, size - 1]区间中
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法
ArrayList支持两种删除元素的方式:
(1)remove(int index): 按照下标删除
public E remove(int index) {
rangeCheck(index); //校验下标是否合法
modCount++;//修改list结构,就需要更新这个值
E oldValue = elementData(index); //根据下标查找值
int numMoved = size - index - 1;//index后面有多少个元素
if (numMoved > 0)
//index后面的所有元素左移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//移动后,原数组中size位置为null
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
(2)remove(Object o):按照元素删除,会删除和参数匹配的第一个元素
public boolean remove(Object o) {
//如果元素是null 遍历数组移除第一个null
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;
}
//根据下标来移除元素
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;//计算index后面有多少个元素
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}