ArrayList
是一个可扩容数组Resizable-array
,它实现了List
接口的全部方法。java
从对
ArrayList
的简单描述中咱们能够得出几点数组
- ArrayList 是数组,但不一样于通常数组,可扩容,而通常数组容量固定。
- ArrayList 实现了
List
接口,因此List
包含的基本方法(新增,删除,插入等)ArrayList都实现了。
ArrayList 内部实现是经过一个对象数组
transient Object[] elementData
ArrayList add() 方法实质上是实现了在数组的末尾添加一个元素elementData[size++] = el
因此执行的时间复杂度是固定的O(1)
,添加n个元素为O(n)
由于本质上是对数组进行操做,当在数组中插入、删除数据时,须要先对数组进行移位操做,插入时须要将插入点之后的数据所有后移,而删除操做则须要将删除节点后的数据所有前移,操做时间复杂度为
O(n)
。
ArrayList 在多线程状况下有线程进行修改时,是线程不安全的。线程安全性问题,取决于如何应用。
List list = Collections.synchronizedList(new ArrayList(...))
能够获取线程安全的ArrayList
ArrayList 有其自动扩容机制也能够在预知要处理数据大小时手动扩容安全
1.自动扩容多线程
ArrayList 在新增元素时(add ,addAll操做)会先检测当前内部数组
elementData[]
的容量是否足够添加新元素,若是不足则扩容,ArrayList最大容量为Integer.MAX_VALUE
,自动扩容调用的流程以下并发public boolean add(E e) { // 自动扩容,并记录元素修改数量 ,元素修改数量主要是用于并发修改错误 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } // 返回一个数组大小的值 minCapacity 和默认的 DEFAULT_CAPACITY 中较大的那个 private static int calculateCapacity(Object[] elementData, int minCapacity) { // 若是是空数组 经过new ArrayList()建立 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 默认大小是DEFAULT_CAPACITY = 10 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }实际扩充容量的片断dom
private void ensureExplicitCapacity(int minCapacity) { modCount++; // 将上面方法返回的大小和当前 elementData 大小进行比较,判断是否须要扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // 具体扩容代码 int oldCapacity = elementData.length; // 扩容后容量老是扩容前的大约1.5倍左右增量0.5 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 若是扩容后超出了最大个数限制 则由hugeCapacity()来处理 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 : MAX_ARRAY_SIZE; }2.手动扩容或者指定初始容量性能
ArrayList 手动扩容主要是调用
ensureCapacity(int minCapacity)
方法,除此以外还能在建立ArrayList时指定初始容量ui建立时直接指定容量
new ArrayList<>(initialCapacity)
及其实现thispublic 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); } }经过
ensureCapacity(int minCapacity)
方法在处理过程当中扩容,这种状况通常发生在处理过程当中发现初始容量不能知足当前数据需求,而又为了不自动扩容时的资源浪费,由于每次自动扩容时都会进行数组复制。线程public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0: DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }3.最后看看扩容时的数组复制,数组复制是经过
Arrays.copyOf(origin,newLenght)
方法来实现的public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } // 具体复制代码 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // 将旧数组中的数据复制到新数组 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
ArrayList在方法中传递时,传递的是对象的引用,当某一个方法修改了ArrayList时,该修改会反映到引用该ArrayList的全部地方,无论传递多少次,都引用的时同一个内存区域的数据,因此若是要实如今传递时确保内容不变,应该克隆(用ArrayList的clone())方法
一份进行传递(须要注意深浅克隆问题深克隆可使用Collections.copy(dest,src)方法
),具体该怎么弄,仍是要视需求而定。
若是要找他们之间的关系可能就是size=元素数量 <= capacity = 容量大小
吧。size
反映的是当前数组中存了多少数据,而capacity
则反映的是ArrayList内部数组的容量。不能习惯性认为capacity 既是 size
ArrayList和LinkedList为何会存在选择性问题,由于他们在get、add、remove
时性能是不同的。ArrayList是内部实现是数组,在
随机存取
时时间复杂度是O(1)
知道索引就能立马取值,而其插入和删除
操做就相对比较麻烦,须要进行移位操做
线性的时间复杂度。LinkedList 是双向连表
Doubly-linked
,在插入和删除
时时间复杂度都是O(1)
,但索引(取索引位上的值)时须要从表头或者表尾进行遍历操做。因此选用哪个,彻底取决于你要进行的操做是以
随机存取
为主仍是增删元素较多
为主。
transient
关键字的做用是阻止对象序列化,那么为何要防止elementData序列化呢?那是由于elementData是一个数组,而且并非数组中每个位置上都保存有值,容量10000的数组中可能只保存了10个对象。因此不能对其进行序列化,在ArrayList中重写了writeObject 、readObject
方法来对ArrayList进行序列化控制。
RandomAccess接口是一个标记接口
并无定义任何方法,ArrayList 实现它是标记ArrayList支持快速随机访问
这一特性!
ConcurrentModificationException
并发修改异常
ConcurrentModificationException
一般出如今对一个List进行遍历时,对正在遍历的数组进行了修改性操做(修改性操做:改变大小(size=数量)的操做,而不是指具体值)(add,remove,clear等)时便会抛出这个异常,在ArrayList内部有一个protected transient int modCount = 0
的变量用于记录对ArrayList的修改,好比add
方法代码public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }能够看到在调用
add
方法时会执行modCount++
操做以此标记最新修改的数量modCount
而在遍历时好比
forEach
先看代码public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }能够看到for循环中一个明确的条件是
modCount == expectedModCount
每次遍历都会检测该条件是否成立,而在进入该段代码以前先用final int expectedModCount = modCount;
来保存代码执行以前的修改数量,当进入遍历后,有了更改操做,就会使得expectedModCount 和modCount不相等
此时便会抛出ConcurrentModificationException
异常,对于其余的修改操做,原理都是相似的。