Java集合詳解(二):ArrayList原理解析

Yanci丶發表於2021-05-24

 概述

  本文是基於jdk8_271版本進行分析的。

  ArrayList是Java集合中出場率最多的一個類。底層是基於陣列實現,根據元素的增加而動態擴容,可以理解為它是加強版的陣列。ArrayList允許元素為null。它是執行緒不安全的。

資料結構

  • 實現繼承關係

1 public class ArrayList<E> extends AbstractList<E>
2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. AbstractList:繼承AbstractList抽象類,使用實現的公共方法
  2. List:實現List介面操作規範,增、刪、遍歷等操作
  3. RandomAccess:提供隨機訪問功能
  4. Cloneable:提供可拷貝功能
  5. Serializable:提供可序列化功能

  這裡有人會問為什麼繼承了AbstractList還要實現List介面(這個問題無關緊要,可以忽略),我在stackoverflow一個帖子看到了這個問題,有興趣可以點選檢視

  •  靜態變數

1     // 預設初始化容量的大小
2     private static final int DEFAULT_CAPACITY = 10;
3     // 空陣列
4     private static final Object[] EMPTY_ELEMENTDATA = {};
5     // 空陣列,與EMPTY_ELEMENTDATA 區分開,無參構造時候會使用該空陣列(jdk1.7是隻有EMPTY_ELEMENTDA他的)
6     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
7     // 容量最大值。-8是因為一些虛擬機器在陣列中保留一些標題字,嘗試分配更大的陣列可能會導致OOM
8     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 成員變數

1     transient Object[] elementData;    //存放陣列元素,transient表示該欄位不進行序列化操作
2     private int size;    //陣列中實際存放元素的個數
  • 構造方法

  無參構造使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA空陣列;帶參構造初始化長度為0,使用的是EMPTY_ELEMENTDATA空陣列。

 1     // 帶參構造,指定初始化容量大小
 2     public ArrayList(int initialCapacity) {
 3         if (initialCapacity > 0) {
 4             this.elementData = new Object[initialCapacity];
 5         } else if (initialCapacity == 0) {
 6             this.elementData = EMPTY_ELEMENTDATA;
 7         } else {
 8             throw new IllegalArgumentException("Illegal Capacity: "+
 9                                                initialCapacity);
10         }
11     }
12 
13     // 無參構造,elementData 使用預設空陣列
14     public ArrayList() {
15         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
16     }
17 
18     // 有參構造。傳入一個集合
19     public ArrayList(Collection<? extends E> c) {
20         Object[] a = c.toArray();
21         if ((size = a.length) != 0) {
22             if (c.getClass() == ArrayList.class) {
23                 elementData = a;
24             } else {
25                 elementData = Arrays.copyOf(a, size, Object[].class);
26             }
27         } else {
28             // 如果傳入集合長度為0,elementData 用EMPTY_ELEMENTDATA替換
29             elementData = EMPTY_ELEMENTDATA;
30         }
31     }

主要方法解析

  • calculateCapacity--擴容方法
    // 計算需要的最小容量大小
    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++;
        if (minCapacity - elementData.length > 0)
            // 如果所需的最小容量值大於現在陣列長度,表示需要進行擴容處理
            grow(minCapacity);
    }

    // 真正擴容方法
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length; // 原容量大小
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 預計擴容的大小;位運算(1.5倍向下取整)
        // 預計擴容的值與通過計算需要的最小容量值比較,取最大值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 容量最大值為MAX_ARRAY_SIZE,為什麼還會返回 Integer.MAX_VALUE呢?
        // 如果minCapacity > MAX_ARRAY_SIZE,說明此時容器大小已經為MAX_ARRAY_SIZE。靜態變數說到MAX_ARRAY_SIZE=Integer.MAX_VALUE-8,一些虛擬機器在陣列中保留一些標題字,嘗試分配更大的陣列可能會導致OOM。注意只是可能,不是所有的,因為此時陣列長度已經是MAX_ARRAY_SIZE了,不如嘗試去進行擴容到Integer.MAX_VALUE,說不定就成功了
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }                            
  • add--新增元素

  注意:在進行新增操作之前,都會先進行判斷陣列是否需要擴容;入參如果涉及索引,還會判斷索引是否越界。

 1     public boolean add(E e) {
 2         ensureCapacityInternal(size + 1);  // Increments modCount!!
 3         elementData[size++] = e;
 4         return true;
 5     }
 6     public void add(int index, E element) {
 7         rangeCheckForAdd(index);
 8         
 9         ensureCapacityInternal(size + 1);  // Increments modCount!!
10         System.arraycopy(elementData, index, elementData, index + 1,size - index);
11         elementData[index] = element;
12         size++;
13     }
14     
15     public boolean addAll(Collection<? extends E> c) {
16         Object[] a = c.toArray();
17         int numNew = a.length;
18         ensureCapacityInternal(size + numNew);  // Increments modCount
19         System.arraycopy(a, 0, elementData, size, numNew);
20         size += numNew;
21         return numNew != 0;
22     }
23     public boolean addAll(int index, Collection<? extends E> c) {
24         rangeCheckForAdd(index);
25 
26         Object[] a = c.toArray();
27         int numNew = a.length;
28         ensureCapacityInternal(size + numNew);  // Increments modCount
29 
30         int numMoved = size - index;
31         if (numMoved > 0)
32             System.arraycopy(elementData, index, elementData, index + numNew,
33                              numMoved);
34 
35         System.arraycopy(a, 0, elementData, index, numNew);
36         size += numNew;
37         return numNew != 0;
38     }
  • remove--刪除元素

  刪除元素時,如果該索引位後面有元素,則將後面元素向前移動一位(如果刪除n個,則向後面元素向前移動n位),同時將陣列最後一位數值置為null。

  1     public E remove(int index) {
  2         rangeCheck(index);
  3 
  4         modCount++;
  5         E oldValue = elementData(index);
  6 
  7         int numMoved = size - index - 1;
  8         if (numMoved > 0)
  9             System.arraycopy(elementData, index+1, elementData, index,
 10                              numMoved);
 11         elementData[--size] = null; // clear to let GC do its work
 12 
 13         return oldValue;
 14     }
 15     public boolean remove(Object o) {
 16         if (o == null) {
 17             for (int index = 0; index < size; index++)
 18                 if (elementData[index] == null) {
 19                     fastRemove(index);
 20                     return true;
 21                 }
 22         } else {
 23             for (int index = 0; index < size; index++)
 24                 if (o.equals(elementData[index])) {
 25                     fastRemove(index);
 26                     return true;
 27                 }
 28         }
 29         return false;
 30     }
 31 
 32     private void fastRemove(int index) {
 33         modCount++;
 34         int numMoved = size - index - 1;
 35         if (numMoved > 0)
 36             System.arraycopy(elementData, index+1, elementData, index,
 37                              numMoved);
 38         elementData[--size] = null; // clear to let GC do its work
 39     }
 40 
 41     public void clear() {
 42         modCount++;
 43 
 44         // clear to let GC do its work
 45         for (int i = 0; i < size; i++)
 46             elementData[i] = null;
 47 
 48         size = 0;
 49     }
 50     
 51     protected void removeRange(int fromIndex, int toIndex) {
 52         modCount++;
 53         int numMoved = size - toIndex;
 54         System.arraycopy(elementData, toIndex, elementData, fromIndex,
 55                          numMoved);
 56 
 57         // clear to let GC do its work
 58         int newSize = size - (toIndex-fromIndex);
 59         for (int i = newSize; i < size; i++) {
 60             elementData[i] = null;
 61         }
 62         size = newSize;
 63     }
 64     
 65     // 從此列表中刪除指定集合c中包含的所有元素
 66     public boolean removeAll(Collection<?> c) {
 67         Objects.requireNonNull(c);
 68         return batchRemove(c, false);
 69     }
 70     // 此列表中僅保留包含在指定集合中的元素
 71     public boolean retainAll(Collection<?> c) {
 72         Objects.requireNonNull(c);
 73         return batchRemove(c, true);
 74     }
 75     // ArrayList的批量刪除演算法
 76     private boolean batchRemove(Collection<?> c, boolean complement) {
 77         final Object[] elementData = this.elementData;
 78         int r = 0, w = 0;
 79         boolean modified = false;
 80         try {
 81             for (; r < size; r++)
 82                 if (c.contains(elementData[r]) == complement)
 83                     elementData[w++] = elementData[r];
 84         } finally {
 85             // Preserve behavioral compatibility with AbstractCollection,
 86             // even if c.contains() throws.
 87             if (r != size) {
 88                 System.arraycopy(elementData, r,
 89                                  elementData, w,
 90                                  size - r);
 91                 w += size - r;
 92             }
 93             if (w != size) {
 94                 // clear to let GC do its work
 95                 for (int i = w; i < size; i++)
 96                     elementData[i] = null;
 97                 modCount += size - w;
 98                 size = w;
 99                 modified = true;
100             }
101         }
102         return modified;
103     }
  • clone--拷貝方法
    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);
        }
    }
  • writeObject/readObject--序列化方法
 1     private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException{
 3         // Write out element count, and any hidden stuff
 4         int expectedModCount = modCount;
 5         s.defaultWriteObject();
 6 
 7         // Write out size as capacity for behavioural compatibility with clone()
 8         s.writeInt(size);
 9 
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14 
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18     }
19 
20     private void readObject(java.io.ObjectInputStream s)
21         throws java.io.IOException, ClassNotFoundException {
22         elementData = EMPTY_ELEMENTDATA;
23 
24         // Read in size, and any hidden stuff
25         s.defaultReadObject();
26 
27         // Read in capacity
28         s.readInt(); // ignored
29 
30         if (size > 0) {
31             // be like clone(), allocate array based upon size not capacity
32             ensureCapacityInternal(size);
33 
34             Object[] a = elementData;
35             // Read in all elements in the proper order.
36             for (int i=0; i<size; i++) {
37                 a[i] = s.readObject();
38             }
39         }
40     }

附錄

ArrayList原始碼詳細註釋Github地址:https://github.com/y2ex/jdk-source/blob/jdk1.8.0_271/src/main/java/java/util/ArrayList.java

jdk1.8原始碼Github地址:https://github.com/y2ex/jdk-source/tree/jdk1.8.0_271

相關文章