ArrayList详解
简介:
概述:ArrayList 的底层是动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacityInternal操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。允许 null 的存在。(可重复、有下标、有序)
它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以 及取第 i 元素的时间复杂度为O(1)
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
ArrayList 实现了Cloneable接口,即覆盖了函数 clone(),能被克隆。ArrayList 实现 java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList, 而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList、Collections.synchronizedList。
成员变量
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//无参构造时存放的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//保存ArrayList数据的数组
transient Object[] elementData;
//集合元素个数
private int size;
//这个变量是定义在 AbstractList 中的。记录对 List 操作的次数。主要使用是在 Iterator,是防止在迭代的过程中集合被修改。
protected transient int modCount = 0;
构造方法
//用户指定大小创建
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
}
//无参构造、默认空数组
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
//构造一个包含指定集合元素的列表
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// 判断c.toArray()返回的是不是object类型的数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
主要方法及解析
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//判断是不是空元素,如果是空元素,再从默认容量10和要添加元素大小中取最大值,进行扩容。
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
由上述源码可知,在每次add添加元素时,都会先通过ensureCapacityInternal()判断容量大小,还够不够添加该元素,如果不够,再调用grow()方法进行扩容。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//先1.5倍扩容该数组(如果该集合是空集合的话,扩容后的大小还是0)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//来比较扩容后的数组大小与添加完元素大小比较,如果还是比添加完元素大小小的话,就直接赋值添加元素的后的容量大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//比较此时的容量大小是否超过Interger类型的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 通过copyOf()将原数组复制到扩完容的新数组上
elementData = Arrays.copyOf(elementData, newCapacity);
}
//如果超过Interge的最大值,就取Integer的最大值作为容量大小,没超过就取原值。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
可见,grow方法先是1.5倍扩容原数组,要是还是不够的话,就直接扩容成添加完元素后数组大小。但不能超过Integer类型的最大值。超过了就取最大值。
public E remove(int index) {
//先判断该下标是否在size元素大小内,不在就报数组下标越界exception
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;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
迭代器 iterator
有使用过集合的都知道,在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。
至于为啥会报ConcurrentModificationException异常,是因为for循环本质上还是走的是Iterator迭代器循环,而Iterator迭代器中在每次通过next()获取下一个元素时,会首先通过checkForComodification方法,判断修改的次数和被期待的修改次数是否相等,不相等则抛出异常。
public E next() {
checkForComodification();/***看这行***/
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//cursor向后挪一位
return (E) elementData[lastRet = i];//lastRet为当前取出的元素所在索引,后面会用到
}
final void checkForComodification() {/***再看这里***/
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
使用iterator迭代器解决该问题:
直接调用 iterator.remove() 即可。因为在该方法中增加了 expectedModCount = modCount 操作。但是这个 remove 方法也有弊端。
1、只能进行remove操作,add、clear 等 Itr 中没有。
2、调用 remove 之前必须先调用 next。因为 remove 开始就对 lastRet 做了校验。而 lastRet 初始化时为 -1。
3、next 之后只可以调用一次 remove。因为 remove 会将 lastRet 重新初始化为 -1
总结
ArrayList 底层基于数组实现容量大小动态可变。 扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
来扩容。 扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。 ArrayList 的最大存储能力:Integer.MAX_VALUE。 size 为集合中存储的元素的个数。elementData.length 为数组长度,表示最多可以存储多少个元素。 如果需要边遍历边 remove ,必须使用 iterator。且 remove 之前必须先 next,next 之后只能用一次 remove