ArrayList
doc
- 列表接口的可调整大小的数组实现。实现所有可选的列表操作,并允许所有元素,包括null。除了实现List接口之外,这个类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相当于Vector,只是它是不同步的。线程不安全)
- size、isEmpty、get、set、iterator和listIterator操作在固定时间内运行。add操作以摊余常量时间运行,即添加n个元素需要O(n)时间。所有其他操作都是在线性时间内运行的(粗略地说)。与LinkedList实现相比,常量因子较低。
- 每个ArrayList实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少和列表大小一样大。当元素被添加到ArrayList时,它的容量会自动增长。除了增加一个元素具有固定的摊余时间成本这一事实外,增长策略的细节没有被指定。
- 应用程序可以在使用ensureCapacity操作添加大量元素之前增加ArrayList实例的容量。这可能会减少增量重新分配的数量。(创建ArrayList集合可以初始自己的容量)
- 请注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,那么它必须在外部同步。(结构修改是指添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅仅设置元素的值不是结构修改。)这通常是通过在自然封装列表的某个对象上同步来完成的。如果不存在这样的对象,则应该使用集合.synchronizedList方法。最好在创建时执行此操作,以防止意外的未同步访问列表:
List list = Collections.synchronizedList(new ArrayList(...));
- 这个类的iterator和listIterator方法返回的迭代器是快速失败的:如果在迭代器创建之后的任何时候,以任何方式(除了通过迭代器自己的remove或add方法)**对列表进行结构修改,迭代器将抛出一个ConcurrentModificationException。(意思就是说ArrayList不适合修改)**因此,在并发修改的情况下,迭代器会迅速而彻底地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。
构造方法属性
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 = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
//集合的数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 可能(错误地)不返回Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
//copy数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 空实例数组此时会默认初始容量为10
this.elementData = EMPTY_ELEMENTDATA;
}
}
动态扩容流程
1.确定内部容量
//该方法重写AbstractList的add方法
public boolean add(E e) {
//确保内部容量(通过判断,如果够则不进行操作;容量不够就扩容来确保内部容量)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //此时将元素依次赋值
return true;
}
ensureCapacityInternal方法名的英文大致是“确保内部容量”,size表示的是执行添加之前的元素个数(我们构造时已经将其赋值),并非ArrayList的容量,容量应该是数组elementData的长度。ensureCapacityInternal该方法通过将现有的元素个数数组的容量比较。看如果需要扩容,则扩容。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果实际存储数组 是空数组,则最小需要容量就是默认容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//如果 最小容量大于当前数组的长度 就需要扩容
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
以上,elementData是用来存储实际内容的数组。minExpand 是最小扩充容量。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA共享的空数组实例用于默认大小的空实例。根据传入的最小需要容量minCapacity来和数组的容量长度对比,若minCapactity大于或等于数组容量,则需要进行扩容。
2.扩容
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
//旧长度
int oldCapacity = elementData.length;
//>>位运算,右移动一位。 整体相当于newCapacity =oldCapacity + 0.5 * oldCapacity
// jdk1.7采用位运算比以前的计算方式更快
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断最大个数 意思就是说你扩容数量不能大于int的存储最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 最重要的复制元素方法
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE ://int的最大值
MAX_ARRAY_SIZE;
}
综上所述:ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个
向数组中添加第一个元素时,新生产数组容量为10.
向数组中添加到第10个元素时,数组容量仍为10.
向数组中添加到第11个元素时,数组容量扩为15.
向数组中添加到第16个元素时,数组容量扩为22.
每次扩容都是通过Arrays.copyOf(elementData, newCapacity) 这样的方式实现的。
总结:当我们在使用无参构造方法在构造ArrayList时,会将this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值,此时由于elementData属性规定当为该属性时
会默认使用初始值容量10。当容量大于初始容量时开始扩容 每次按照1.5倍(位运算)的比率通过copeOf的方式扩容 以上就是动态扩容的原理。
删除指定元素
public E remove(int index) {
//判断下标是不是超出size
rangeCheck(index);
//该数据结构被修改的次数
modCount++;
//取出被删除的值
E oldValue = elementData(index);
//获取新数组长度
int numMoved = size - index - 1;
if (numMoved > 0)
//copy
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) {
//为null
if (o == null) {
for (int index = 0; index < size; index++)
//删除null
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;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
如上:可以看出对于 ArrayList 元素的删除操作,需要将被删除元素的后续元素向前移动,代
价比较高