◆
ArrayList簡介
◆
ArrayList 是一個陣列佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些介面。
- AbstractList、List提供了新增、刪除、修改、遍歷等功能。
- RandmoAccess提供了隨機訪問功能
- Cloneable提供了可以被克隆的功能
- Serializable提供了序列化的功能
- 和Vector不同,ArrayList中的操作不是執行緒安全的!所以,建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或CopyOnWriteArrayList。
◆
ArrayList的屬性
◆
1234567891011121314151617181920212223242526272829303132複製程式碼
| /** * 陣列預設的大小 */
private static final int DEFAULT_CAPACITY = 10;
/** * 使用陣列大小為0時的預設緩衝區 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/** * 使用ArrayList(int initialCapacity)構造方法時且initialCapacity為0時緩衝區 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** * 真實儲存arraylist元素的陣列緩衝區 */
transient Object[] elementData;
// non-private to simplify nested class access
/** * List的實際大小 */
private int size;
/** * 陣列可分配的最大大小 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/** * 特別注意這個是繼承自AbstractList的屬性,用來記錄List被修改的次數 */
protected transient int modCount = 0;複製程式碼
|
◆
ArrayList的構造方法
◆
1234567891011121314151617181920212223242526272829303132333435複製程式碼
| /** * 無參構造方法,初始化elementData */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_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);
}}/** * 建立一個包含collection的ArrayList */
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else { //replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}}複製程式碼
|
◆
ArrayList的方法
◆
接下來我們就以ArrayList的幾個比較經典的方法來看一下它是如何設計的。
首先是新增方法,新增的方法一共有3個:
123456789101112131415161718192021222324252627282930313233343536複製程式碼
| /** * 新增元素 */ public boolean add(E e) { //計算陣列最新的容量,以及判斷是否需要擴容 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * 指定索引新增元素 */ 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; //List長度+1 size++; } /** * 新增一個集合 */ 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; }複製程式碼
|
仔細觀察上方三個新增的方法,它們都呼叫了ensureCapacityInternal方法,這個方法的引數是執行當前新增操作所需要的陣列容量。它會根據傳遞的引數來計算陣列是否需要擴容,如果需要擴容則完成擴容操作。
不同之處在於,上方的兩個方法新增的只有一個元素,所以傳的size+1,而addAll因為是新增的一個集合所以傳的引數是size+集合的長度。
接著看這個方法的實現:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152複製程式碼
| /** * 計算陣列最新的容量 * @param minCapacity */ private void ensureCapacityInternal(int minCapacity) { //如果建立ArrayList時指定大小為0 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果本次新增的大小比初始容量10大的話則不使用預設的容量10,直接使用本次新增的大小作為初始容量 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } /** * 記錄修改次數,呼叫擴容方法 * @param minCapacity */ private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //擴容 grow(minCapacity); } /** * 擴容 */ private void grow(int minCapacity) { // 獲取原來的陣列長度 int oldCapacity = elementData.length; //新容量設定為老容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新容量還不夠存放本次需要新增的大小,則直接擴容到本次新增的大小 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新容量超出陣列最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 呼叫Arrays的複製方法更新資料緩衝池 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; }複製程式碼
|
以上就是ArrayList動態擴容的實現方式了,這裡注意一下擴容是通過新建一個陣列來替換原先的陣列來進行的:
1複製程式碼
| elementData = Arrays.copyOf(elementData, newCapacity);複製程式碼
|
接下來看刪除操作:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152複製程式碼
| /** * 遍歷陣列,找出需要刪除的元素的索引,並呼叫刪除方法 */ 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; }/** * 刪除指定索引的元素 * */ 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; } /* * 刪除指定元素 */ 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 }複製程式碼
|
需要注意的是刪除一個元素也是通過底層的方法實現的。
接著看get和set相對就比較簡單了。
1234567891011121314151617181920複製程式碼
| public E get(int index) { //判斷索引是否越界 rangeCheck(index); return elementData(index); } /** * 判斷索引是否越界 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } public E set(int index, E element) { //判斷索引是否越界 rangeCheck(index); //獲取此索引原先的值 E oldValue = elementData(index); elementData[index] = element; return oldValue; }複製程式碼
|
看了ArrayList的增刪改查方法相信你已經明白了為什麼一直有人告訴你ArrayList查詢修改效率高而新增和刪除效率低了。
ArrayList的序列化方式同樣是比較有意思的,一開始看到ArrayList實現了Serializable我們就知道它是可以序列化的,但是實際儲存的陣列elementData卻是transient,觀看下方程式碼你就可以找到答案:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748複製程式碼
| /** * 將List寫入s,注意先寫容量,然後在寫資料 * @param s * @throws java.io.IOException */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // 首先寫陣列容量 s.writeInt(size); // 遍歷寫陣列中的元素 for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * 讀取s中的List */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // 首先讀陣列容量 s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }複製程式碼
|
鑑於篇幅有限,本篇文章僅列出上方部分程式碼,ArrayList完整原始碼解析請看:github.com/shiyujun/sy…!!!
部落格所有文章首發於公眾號《Java學習錄》轉載請保留
掃碼關注公眾號即可領取2000GJava學習資源