jdk原始碼分析之ArrayList
ArrayList關鍵屬性分析
ArrayList採用Object陣列來儲存資料
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* @serial
*/
private int size;
Object[] elementData是一個buffer陣列,用來儲存ArrayList的資料,該陣列的大小表示ArrayList的容量,而size屬性表示的是ArrayList裡邊儲存元素的個數。
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
多個ArrayList例項共享的static屬性,一個空陣列的例項,使用ArrayList的無參建構函式建立ArrayList例項的時候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA給底層陣列elementData賦值
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
而當建立一個容量為0的ArrayList時,直接將層陣列elementData賦值為另外一個static屬性
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
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);
}
}
從ArrayList獲取資料get(int index)
ArrayList可以通過下標對資料進行隨機訪問,時間複雜度O(1),實現方法為get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
get方法很簡單,輸入引數合法的話直接返回底層陣列elementData對應位置的元素即可。
而rangeCheck主要是檢查下標不能越界訪問
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
向ArrayList尾部新增一個資料add(E e)
新增資料有兩個方法,呼叫add(E e) 向ArrayList末尾新增資料和呼叫add(int index, E element)新增資料到指定位置
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
新增資料首先檢查是不是需要擴充容量來新增新的資料,呼叫ensureCapacityInternal(size + 1)確保當前容量足夠新增一個新的資料,然後elementData[size++] = e將新資料新增在陣列的末尾
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal確保在ArrayList容量為0的時候新增資料擴容時,至少擴容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小預設為10
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
然後呼叫 ensureExplicitCapacity(minCapacity)進行擴容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++用於記錄修改次數,主要用於一個執行緒使用迭代器迭代資料的時候另一個資料更改ArrayList結構丟擲ConcurrentModificationException異常
if (minCapacity - elementData.length > 0)用於判斷是否真的需要擴容,elementData.length表示的是容器容量,size表示容器儲存資料的數量,而minCapacity =sie+1,因此elementData如果有多於一個位置空閒沒有儲存資料,就不需要擴容
否則呼叫grow(minCapacity)進行真正的擴容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
可以看到擴容時直接將容量大小變為之前的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
這裡還需要對newCapacity進行調整,如果擴容後的newCapacity還是比minCapacity小
滿足
if (newCapacity - minCapacity < 0)
設定newCapacity為傳進來的引數minCapacity
newCapacity = minCapacity;
然後判斷現在的newCapacity是否比上限MAX_ARRAY_SIZE大
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果newCapacity比MAX_ARRAY_SIZE還大,則需要呼叫hugeCapacity(minCapacity)判斷是否是minCapacity傳入負數溢位
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
hugeCapacity(int minCapacity)首先檢測是否溢位,沒溢位的話就返回Integer.MAX_VALUE 作為最大值(minCapacity > MAX_ARRAY_SIZE條件在呼叫出就滿足)
經過上述一系列步驟,最終滿足各種條件的新容量值minCapacity得到滿足
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
這裡是直接建立一個容量為newCapacity的新陣列,把之前elementData儲存的資料複製到新陣列。陣列的複製是比較耗費效能的,因此應該避免連續擴容,儘量呼叫有參建構函式ArrayList(int initialCapacity)並設定合理容量大小
向ArrayList任意位置新增一個資料add(int index, E element)
呼叫add(int index, E element)在ArrayList任意位置新增資料
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
傳入引數合法性檢查仍然是方法體第一件要做的事情,這裡要檢測插入資料的位置index是否合法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
引數合法性通過後呼叫ensureCapacityInternal(size + 1)進行擴容,上邊已經分析過擴容的固定套路。擴容後就是呼叫System.arraycopy進行陣列的複製,由此可見ArrayList新增資料是比較耗費效能的,事件複雜度是O(n),因為插入資料總是伴隨著陣列的複製(陣列元素的移動)。
在ArrayList尾部新增一個Collection集合addAll
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
先通過Collection的toArray方法把Collection轉化為Object陣列,然後通過ensureCapacityInternal(size + numNew)進行擴容,然後呼叫
System.arraycopy(a, 0, elementData, size, numNew);
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
System.arraycopy進行陣列的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示從陣列a的下標0出開始複製length個元素導陣列elementData的下標size處。
在ArrayList任意位置新增一個集合Collection
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
仍然是先做入口引數的合法性檢查
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
然後把集合轉化為Object陣列
Object[] a = c.toArray();
通過ensureCapacityInternal(size + numNew)進行擴容
計算原陣列需要移動的資料的個數int numMoved = size - index
如果有資料需要移動,呼叫System.arraycopy進行資料整體移動
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
從elementData陣列下標為index出複製numMoved個資料到elementData陣列的下標為index + numNew處
這樣elementData陣列下標為index到index+numNew-1的位置被空出來,用於放置新的資料
然後集合的資料新增到elementData陣列留出的位置即可
System.arraycopy(a, 0, elementData, index, numNew);
從陣列a的下標為0的地方複製numNew個資料到elementData陣列的下標為index處
ArrayList中獲取某一資料的下標indexOf
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
獲取下標只能通過遍歷的方式逐一比較,null資料不能呼叫方法,所以不能通過equals進行比較,所以分類討論,
如果傳入的引數o是是null的話通過elementData[i]==null進行比較,否則通過o.equals(elementData[i])進行比較。
如果遍歷過程中找到該資料,返回該資料下標,遍歷結束沒有找到返回-1
注意indexOf是找到第一個相等的資料就直接返回下標了,如果想找到該資料在ArrayList中的最後一個位置,使用lastIndexOf即可
清空ArrayList的全部資料clear()
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
首先modCount++,這是如果其他執行緒正在通過迭代器進行資料的訪問,檢測到modCount發生了變化將會丟擲ConcurrentModificationException
然後設定陣列中的每一個元素為null,方便垃圾回收,最後設定size=0;
雖然資料全部置null,size歸0,但是elementData的大小沒有變化
ArrayList的克隆clone()
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
可以看到clone方法只是進行了淺克隆,在某些需要深克隆的場景出,需要自己負責每一個元素的深克隆
ArrayList刪除指定位置的資料
public E remove(int index) {
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;
}
程式碼流程還是比較清晰
下標檢查,modCount++達到迭代器快速失敗,陣列元素的移動,返回舊值
其中比較重要的一點就是
elementData[–size] = null; // clear to let GC do its work
Effective Java提到了這點,自己申請記憶體自己要記得管理,否則造成記憶體洩露
ArrayList刪除某一個資料
public boolean remove(Object o) {
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;
}
這裡同樣是根據傳入的引數o是否為null進行分類處理,找到元素所在的位置index後直接呼叫 fastRemove(index)進行資料的刪除,fastRemove比remove不需要檢查陣列下標
ArrayList的排序sort
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
先儲存modCount,如果modCount在排序過程中被改變,丟擲ConcurrentModificationException異常
排序是直接呼叫Arrays.sort方法
ArrayList的縮容trimToSize()
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
先登記modCount++是迭代器可以快速失敗
然後通過 Arrays.copyOf進行陣列建立和複製,使ArrayList的容量等於儲存的資料的數量的大小
相關文章
- ArrayList原始碼分析(JDK1.8)原始碼JDK
- 原始碼分析–ArrayList(JDK1.8)原始碼JDK
- ArrayList原始碼分析 jdk1.8原始碼JDK
- 【集合框架】JDK1.8原始碼分析之ArrayList(六)框架JDK原始碼
- 原始碼分析之ArrayList原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- jdk原始碼分析之PriorityQueueJDK原始碼
- jdk原始碼分析之WeakHashMapJDK原始碼HashMap
- jdk原始碼分析之HashMapJDK原始碼HashMap
- jdk原始碼分析之CopyOnWriteArrayListJDK原始碼
- ArrayList 原始碼分析原始碼
- [原始碼分析]ArrayList原始碼
- ArrayList原始碼分析原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- 面試必會之ArrayList原始碼分析以及手寫ArrayList面試原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- jdk原始碼分析之LinkedListJDK原始碼
- jdk原始碼分析之ConcurrentHashMapJDK原始碼HashMap
- jdk原始碼分析之LinkedHashMapJDK原始碼HashMap
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- ArrayList方法原始碼分析原始碼
- ArrayList-原始碼分析原始碼
- Java集合-ArrayList原始碼解析-JDK1.8Java原始碼JDK
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- jdk1.8原始碼之HashMap分析JDK原始碼HashMap
- ArrayList詳解-原始碼分析原始碼
- 【Java集合】ArrayList原始碼分析Java原始碼
- JAVA集合:ArrayList原始碼分析Java原始碼
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- 面試必備:ArrayList原始碼解析(JDK8)面試原始碼JDK
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(1)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(2)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(3)JDK原始碼
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- java基礎:ArrayList — 原始碼分析Java原始碼