1. 程式人生 > >JAVA集合-Lsit集合分析-ArrayList

JAVA集合-Lsit集合分析-ArrayList

ArrayList 特點:

1.ArrayList 繼承了AbstractList,所以是非執行緒安全。

2.ArrayList 實現了RandomAccess介面,所以可以隨機訪問(即通過下標進行訪問,獲取元素)

3.ArrayList 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。

4.ArrayList 實現java.io.Serializable介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。

1.1 類中的屬性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
   
    private static final long serialVersionUID = 8683452581122892189L; // 版本號
   
    private static final int DEFAULT_CAPACITY = 10; // 預設容量
  
    private static final Object[] EMPTY_ELEMENTDATA = {};  // 空物件陣列

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 預設空物件陣列
    
    transient Object[] elementData;// 元素陣列
  
    private int size;  // 實際元素大小,預設為0
   
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大陣列容量
}

1.2構造方法

1.ArrayList()  2.ArrayList(int)  3.ArrayList(Collection<? extends E> c)

1.無參構造方法

/**
    * Constructs an empty list with an initial capacity of ten.  這裡就說明了預設會給10的大小,所以說一開始arrayList的容量是10.
    */
    //ArrayList中儲存資料的其實就是一個數組,這個陣列就是elementData,在123行定義的 private transient Object[] elementData;
   public ArrayList() {  
        super();        //呼叫父類中的無參構造方法,父類中的是個空的構造方法
        this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:是個空的Object[], 將elementData初始化,elementData也是個Object[]型別。空的Object[]會給預設大小10,等會會解釋什麼時候賦值的。
}    

2.有參構造方法一

 public ArrayList(int initialCapacity) {
        super(); //父類中空的構造方法
        if (initialCapacity < 0)    //判斷如果自定義大小的容量小於0,則報下面這個非法資料異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity]; //將自定義的容量大小當成初始化elementData的大小
    }

3.有參構造方法二

  public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();    //轉換為陣列
        size = elementData.length;   //陣列中的資料個數
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class) //每個集合的toarray()的實現方法不一樣,所以需要判斷一下,如果不是Object[].class型別,那麼久需要使用ArrayList中的方法去改造一下。
            elementData = Arrays.copyOf(elementData, size, Object[].class);//複製資料到elementData中
    }

1.1總結:3個構造方法都是為了初始化elementData陣列

2.常用方法

2.1 四種add()方法

①boolean add(E) ; ②void add(int,E) ③ boolean add(int,Collection<? extends E> ) ④boolean addAll(Collection<? extends E>)

2.1.1 boolean add(E);//預設直接在末尾新增元素

 public boolean add(E e) {    
    //確定內部容量是否夠了,size是陣列中資料的個數,因為要新增一個元素,所以size+1,先判斷size+1的這個個數陣列能否放得下,就在這個方法中去判斷是否陣列.length是否夠用了。
        ensureCapacityInternal(size + 1);  
     //在資料中正確的位置上放上元素e,並且size++
        elementData[size++] = e;
        return true;
    }

                     ensureCapacityInternal(xxx); 確定內部容量的方法 

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) { //看,判斷初始化的elementData是不是空的陣列,也就是沒有長度
    //因為如果是空的話,minCapacity=size+1;其實就是等於1,空的陣列沒有長度就存放不了,所以就將minCapacity變成10,也就是預設大小,但是帶這裡,還沒有真正的初始化這個elementData的大小。
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    //確認實際的容量,上面只是將minCapacity=10,這個方法就是真正的判斷elementData是否夠用
        ensureExplicitCapacity(minCapacity);
    }

                 ensureExplicitCapacity(xxx);

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//修改次數加一

    
//minCapacity如果大於了實際elementData的長度,那麼就說明elementData陣列的長度不夠用,不夠用那麼就要增加elementData的length。這裡有的同學就會模糊minCapacity到底是什麼呢,這裡給你們分析一下

/*第一種情況:由於elementData初始化時是空的陣列,那麼第一次add的時候,minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量ensureCapacityInternal)就會判斷出是空的陣列,就會給
  將minCapacity=10,到這一步為止,還沒有改變elementData的大小。
 第二種情況:elementData不是空的陣列了,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表著elementData中增加之後的實際資料個數,拿著它判斷elementData的length是否夠用,如果length
不夠用,那麼肯定要擴大容量,不然增加的這個元素就會溢位。
*/


        if (minCapacity - elementData.length > 0)//需要的長度>陣列的長度
    //arrayList能自動擴充套件大小的關鍵方法就在這裡了
            grow(minCapacity);
    }

                         grow(xxx); arrayList核心的方法,能擴充套件陣列大小。

private void grow(int minCapacity) {
       
        int oldCapacity = elementData.length;  //將擴充前的elementData大小給oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
        if (newCapacity - minCapacity < 0)//這句話就是適應於elementData就空陣列的時候,length=0,那麼oldCapacity=0,newCapacity=0,所以這個判斷成立,在這裡就是真正的初始化elementData的大小了,就是為10.前面的工作都是準備工作。
            newCapacity = minCapacity;.
        if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超過了最大的容量限制,就呼叫hugeCapacity,也就是將能給的最大值給newCapacity
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
    //新的容量大小已經確定好了,就copy陣列,改變容量大小咯。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

                   hugeCapacity()  將陣列擴充為最大值

//這個就是上面用到的方法,很簡單,就是用來賦最大值。
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
//如果minCapacity都大於MAX_ARRAY_SIZE,那麼就Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。因為maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。
//Integer.MAX_VALUE:2147483647   MAX_ARRAY_SIZE:2147483639  也就是說最大也就能給到第一個數值。還是超過了這個限制,就要溢位了。相當於arraylist給了兩層防護。
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }

2.1.2 boolean add(E);//預設直接在末尾新增元素

public void add(int index, E element) {
        rangeCheckForAdd(index);//檢查index也就是插入的位置是否合理。

//跟上面的分析一樣,具體看上面
        ensureCapacityInternal(size + 1);  // Increments modCount!!
//這個方法就是用來在插入元素之後,要將index之後的元素都往後移一位,
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
//在目標位置上存放元素
        elementData[index] = element;
        size++;//size增加1
    }

                rangeCheckForAdd(index) 

 

  private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)   //插入的位置肯定不能大於size 和小於0
//如果是,就報這個越界異常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

 System.arraycopy(...):就是將elementData在插入位置後的所有元素往後面移一位。

public static void arraycopy(Object src,
             int srcPos,
             Object dest,
             int destPos,
             int length)
src:源物件
srcPos:源物件物件的起始位置
dest:目標物件
destPost:目標物件的起始位置
length:從起始位置往後複製的長度。

add()方法流程總結:

2.2刪除方法

2.1.1 remove(int):通過刪除指定位置上的元素

public E remove(int index) {
        rangeCheck(index);//檢查index的合理性

        modCount++;//這個作用很多,比如用來檢測快速失敗的一種標誌。
        E oldValue = elementData(index);//通過索引直接找到該元素

        int numMoved = size - index - 1;//計算要移動的位數。
        if (numMoved > 0)
//這個方法也已經解釋過了,就是用來移動元素的。
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
//將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。
        elementData[--size] = null; // clear to let GC do its work
//返回刪除的元素。
        return oldValue;
    }

2.1.2 remove(Object):這個方法可以看出來,arrayList是可以存放null值得。

//感覺這個不怎麼要分析吧,都看得懂,就是通過元素來刪除該元素,就依次遍歷,如果有這個元素,就將該元素的索引傳給fastRemobe(index),使用這個方法來刪除該元素,
//fastRemove(index)方法的內部跟remove(index)的實現幾乎一樣,這裡最主要是知道arrayList可以儲存null值
     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;
    }

2.1.3 clear():將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉,所以叫clear

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

2.1.4 removeAll(collection c)

 public boolean removeAll(Collection<?> c) {
         return batchRemove(c, false);//批量刪除
     }

                  batchRemove(xx,xx):用於兩個方法,一個removeAll():它只清楚指定集合中的元素,retainAll()用來測試兩個集合是否有交集。 

//這個方法,用於兩處地方,如果complement為false,則用於removeAll如果為true,則給retainAll()用,retainAll()是用來檢測兩個集合是否有交集的。
   private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData; //將原集合,記名為A
        int r = 0, w = 0;   //r用來控制迴圈,w是記錄有多少個交集
        boolean modified = false;  
        try {
            for (; r < size; r++)
//引數中的集合C一次檢測集合A中的元素是否有,
                if (c.contains(elementData[r]) == complement)
//有的話,就給集合A
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
//如果contains方法使用過程報異常
            if (r != size) {
//將剩下的元素都賦值給集合A,
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
//這裡有兩個用途,在removeAll()時,w一直為0,就直接跟clear一樣,全是為null。
//retainAll():沒有一個交集返回true,有交集但不全交也返回true,而兩個集合相等的時候,返回false,所以不能根據返回值來確認兩個集合是否有交集,而是通過原集合的大小是否發生改變來判斷,如果原集合中還有元素,則代表有交集,而元集合沒有元素了,說明兩個集合沒有交集。
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

總結::remove函式使用者移除指定下標的元素,此時會把指定下標到陣列末尾的元素向前移動一個單位,並且會把陣列最後一個元素設定為null

2.3 set()方法

public E set(int index, E element) {
        // 檢驗索引是否合法
        rangeCheck(index);
        // 舊值
        E oldValue = elementData(index);
        // 賦新值
        elementData[index] = element;
        // 返回舊值
        return oldValue;
    }

2.4 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;
    }

2.5  get(int index)

public E get(int index) {
        // 檢驗索引是否合法
        rangeCheck(index);

        return elementData(index);
    }

說明:get函式會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的是,在get函式中存在element函式,element函式用於返回具體的元素,具體函式如下:

E elementData(int index) {
        return (E) elementData[index];
    }

3.總結

1.ArrayList 可以存放null

2.ArrayList本質是一個叫elementData陣列,所以查詢會很快,而刪除,增加會很慢

3.ArrayList 能夠自動擴容 關鍵在於gorw()方法。