在以前的文章中咱們提到過ArrayList,ArrayList能夠說是每個學java的人使用最多最熟練的集合了,可是知其然不知其因此然。關於ArrayList的具體實現,一些基本的都也知道,譬如數組實現,線程不安全等等,可是更加具體的就不多去了解了,例如:初始化的長度,擴容等。java
本篇主要經過一些對源碼的分析,講解幾個ArrayList常見的方法,以及和Vector的區別。算法
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 複製代碼
ArrayList其實是一個動態數組,容量能夠動態的增加,其繼承了AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。數組
RandomAccess接口,被List實現以後,代表List提供了隨機訪問功能,也就是經過下標獲取元素對象的功能。緩存
1.RandomAccess接口,標記接口,代表List提供了隨機訪問功能,也就是經過下標獲取元素對象的功能。之因此是標記接口,是該類原本就具備某項能力,使用接口對其進行標籤化,便於其餘的類對其進行識別(instanceof)。 2.ArrayList數組實現,自己就有經過下標隨機訪問任意元素的功能。那麼須要細節上注意的就是隨機下標訪問和順序下標訪問(LinkedList)的不一樣了。也就是爲何LinkedList最好不要使用循環遍歷,而是用迭代器遍歷的緣由。 3.實現RandomAccess同時意味着一些算法能夠經過類型判斷進行一些針對性優化,例子有Collections的shuffle方法,源代碼就不粘貼了,簡單說就是,若是實現RandomAccess接口就下標遍歷,反之迭代器遍歷 實現了Cloneable, java.io.Serializable意味着能夠被克隆和序列化。安全
在使用中咱們常常須要new出來各類泛型的ArrayList,那麼在初始化過程是怎樣的呢?多線程
以下一行代碼,建立一個ArrayList對象dom
List<Person> list = new ArrayList<>();
複製代碼
咱們來看源碼,是如何初始化的,找到構造方法工具
//無參構造方法
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
複製代碼
看到這些代碼的時候,我也是不解的,elementData和EMPTY_ELEMENTDATA是什麼啊?可是很明顯EMPTY_ELEMENTDATA是一個常量,追本溯源咱們去看一下成員屬性。性能
//若是是無參構造方法建立對象的話,ArrayList的初始化長度爲10,這是一個靜態常量
private static final int DEFAULT_CAPACITY = 10;
//在這裏能夠看到咱們不解的EMPTY_ELEMENTDATA其實是一個空的對象數組
private static final Object[] EMPTY_ELEMENTDATA = {};
//保存ArrayList數據的對象數組緩衝區 空的ArrayList的elementData = EMPTY_ELEMENTDATA 這就是爲何說ArrayList底層是數組實現的了。elementData的初始容量爲10,大小會根據ArrayList容量的增加而動態的增加。
private transient Object[] elementData;
//集合的長度
private int size;
複製代碼
執行完構造方法,以下圖優化
能夠說ArrayList的做者真的是很貼心,連緩存都處理好了,屢次new出來的新對象,都執行同一個引用。
/** * Appends the specified element to the end of this list. */
//增長元素到集合的最後
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//由於++運算符的特色 先使用後運算 這裏其實是
//elementData[size] = e
//size+1
elementData[size++] = e;
return true;
}
複製代碼
先無論ensureCapacityInternal的話,這個方法就是將一個元素增長到數組的size++位置上。
再說回ensureCapacityInternal,它是用來擴容的,準確說是用來進行擴容檢查的。下面咱們來看一下整個擴容的過程
//初始長度是10,size默認值0,假定添加的是第一個元素,那麼minCapacity=1 這是最小容量 若是小於這個容量就會報錯
//若是底層數組就是默認數組,那麼選一個大的值,就是10
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//調用另外一個方法ensureExplicitCapacity
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//記錄修改的次數
modCount++;
// overflow-conscious code
//若是傳過來的值大於初始長度 執行grow方法(參數爲傳過來的值)擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//真正的擴容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新的容量是在原有的容量基礎上+50% 右移一位就是二分之一
int newCapacity = oldCapacity + (oldCapacity >> 1);
//若是新容量小於最小容量,按照最小容量進行擴容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//這裏是重點 調用工具類Arrays的copyOf擴容
elementData = Arrays.copyOf(elementData, newCapacity);
}
//Arrays
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
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;
}
複製代碼
添加到指定的位置
public void add(int index, E element) {
//判斷索引是否越界,若是越界就會拋出下標越界異常
rangeCheckForAdd(index);
//擴容檢查
ensureCapacityInternal(size + 1); // Increments modCount!!
//將指定下標空出 具體做法就是index及其後的全部元素後移一位
System.arraycopy(elementData, index, elementData, index + 1,size - index);
//將要添加元素賦值到空出來的指定下標處
elementData[index] = element;
//長度加1
size++;
}
//判斷是否出現下標是否越界
private void rangeCheckForAdd(int index) {
//若是下標超過了集合的尺寸 或者 小於0就是越界
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
複製代碼
ArrayList支持兩種刪除元素的方式
下面咱們看一下ArrayList的實現
/** 移除list中指定位置的元素 * Removes the element at the specified position in this list. 全部後續元素左移 下標減1 * Shifts any subsequent elements to the left (subtracts one from their * indices). *參數是要移除元素的下標 * @param index the index of the element to be removed 返回值是被移除的元素 * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */
public E remove(int index) {
//下標越界檢查
rangeCheck(index);
//修改次數統計
modCount++;
//獲取這個下標上的值
E oldValue = elementData(index);
//計算出須要移動的元素個數 (由於須要將後續元素左移一位 此處計算出來的是後續元素的位數)
int numMoved = size - index - 1;
//若是這個值大於0 說明後續有元素須要左移 是0說明被移除的對象就是最後一位元素
if (numMoved > 0)
//索引index只有的全部元素左移一位 覆蓋掉index位置上的元素
System.arraycopy(elementData, index+1, elementData, index,numMoved);
// 將最後一個元素賦值爲null 這樣就能夠被gc回收了
elementData[--size] = null; // clear to let GC do its work
//返回index位置上的元素
return oldValue;
}
//移除特定元素
public boolean remove(Object o) {
//若是元素是null 遍歷數組移除第一個null
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//遍歷找到第一個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
}
複製代碼
transient修飾的屬性意味着不會被序列化,也就是說在序列化ArrayList的時候,不序列化elementData。
爲何要這麼作呢?
因此說不是不序列化 而是不所有序列化。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
複製代碼
那麼問題來了
ArrayList有沒有辦法線程安全?
Collections工具類有一個synchronizedList方法
能夠把list變爲線程安全的集合,可是意義不大,由於可使用Vector
Vector爲何是線程安全的?
老實講,拋開多線程 它倆區別沒多大,可是多線程下就不同了,那麼Vector是如何實現線程安全的,咱們來看幾個關鍵方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
複製代碼
就代碼實現上來講,和ArrayList並無不少邏輯上的區別,可是在Vector的關鍵方法都使用了synchronized修飾。