Java容器二.ArrayList源碼學(xué)習(xí)-JDK8

按照從構(gòu)造方法->常用API(增、刪、改、查)的順序來閱讀源碼,并做簡要分析。

一. 定義

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

介紹

  • 繼承了AbstractList,實現(xiàn)了List
    它是一個數(shù)組隊列,提供了相關(guān)的添加、刪除、修改、遍歷等功能
  • 實現(xiàn)了RandmoAccess接口
    即提供了隨機(jī)訪問功能
  • 實現(xiàn)了Cloneable接口
    即覆蓋了函數(shù)clone(),能被克隆
  • 實現(xiàn)java.io.Serializable接口
    支持序列化,能通過序列化去傳輸

優(yōu)勢

  • 比數(shù)組的優(yōu)勢
    ArrayList 是一個數(shù)組隊列,相當(dāng)于動態(tài)數(shù)組。與Java中的數(shù)組相比,它的容量能動態(tài)增長
  • 比Vector的優(yōu)勢
    和Vector不同,ArrayList中的操作不是線程安全的,但是開銷小,在單線程環(huán)境性能優(yōu)于Vector

二 .屬性

底層用數(shù)組來保存數(shù)據(jù)

private transient Object[] elementData;
private int size;
  • size :當(dāng)前數(shù)組長度
  • elementData:存放數(shù)組的對象的數(shù)組
  • modCount:ArrayList集合的修改次數(shù)

基于數(shù)組實現(xiàn),保存元素的數(shù)組使用 transient 修飾,該關(guān)鍵字聲明數(shù)組默認(rèn)不會被序列化。這是 ArrayList 具有動態(tài)擴(kuò)容特性,因此保存元素的數(shù)組不一定都會被使用,那么就沒必要全部進(jìn)行序列化。ArrayList 重寫了 writeObject() 和 readObject() 來控制只序列化數(shù)組中有元素填充那部分內(nèi)容。

三. 構(gòu)造函數(shù)

1)無參構(gòu)造函數(shù)--ArrayList()

Constructs an empty list with an initial capacity of ten.

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.

private static final int DEFAULT_CAPACITY = 10;

此時我們創(chuàng)建的ArrayList對象中的elementData中的長度是1,size是0,當(dāng)進(jìn)行第一次add的時候,elementData將會變成默認(rèn)的長度:10.(具體過程看下文的add())

2)帶容量大小的構(gòu)造函數(shù)--ArrayList(int initialCapacity)

傳入?yún)?shù)如果是大于等于0,則使用用戶的參數(shù)初始化,如果用戶傳入的參數(shù)小于0,則拋出異常

Constructs an empty list with the specified initial capacity.

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

3)帶Collection對象的構(gòu)造函數(shù)--ArrayList(Collection<? extends E> c)

構(gòu)造一個包含指定元素的list,這些元素的是按照Collection的迭代器返回的順序排列的

  1. 將collection對象轉(zhuǎn)換成數(shù)組,然后將數(shù)組的地址的賦給elementData
  2. 更新size的值,同時判斷size的大小
    2.1 如果是size等于0,直接將空對象EMPTY_ELEMENTDATA的地址賦給elementData
    2.2 如果size的值大于0,則執(zhí)行Arrays.copy方法,把collection對象的內(nèi)容copy到elementData中

Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    //因為size代表的是集合元素數(shù)量,所以通過別的集合來構(gòu)造ArrayList時,要給size賦值
    if ((size = elementData.length) != 0) {
        //這里是當(dāng)c.toArray出錯,沒有返回Object[]時
        //利用Arrays.copyOf 來復(fù)制集合c中的元素到elementData數(shù)組中
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
private static final Object[] EMPTY_ELEMENTDATA = {};

Arrays.copyOf()方法

   public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
 
    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;
    }

可以看到,他是在方法內(nèi)部,新建了一個相同類型的數(shù)組,然后調(diào)用系統(tǒng)方法

System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));

original - 源數(shù)組。
0 - 源數(shù)組中的起始位置。
copy - 目標(biāo)數(shù)組。
0 - 目標(biāo)數(shù)據(jù)中的起始位置。
Math.min(original.length, newLength) - 要復(fù)制的數(shù)組元素的數(shù)量。

四. 增加---add addAll

添加元素時使用 ensureCapacity() 方法來保證容量足夠,如果不夠時,需要使用 grow() 方法進(jìn)行擴(kuò)容,使得新容量為舊容量的 1.5 倍oldCapacity + (oldCapacity >> 1)
擴(kuò)容操作需要把原數(shù)組整個復(fù)制到新數(shù)組中,因此最好在創(chuàng)建 ArrayList 對象時就指定大概的容量大小,減少擴(kuò)容操作的次數(shù)。

添加一個元素(在末尾)--add(E e)

add(E e)

Appends the specified element to the end of this list.

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;//在數(shù)組末尾追加一個元素,并修改size
    return true;
}

ensureCapacityInternal(int minCapacity)

  • 確保添加的元素有地方存儲
  • 當(dāng)?shù)谝淮翁砑釉氐臅r候this.size+1 的值是1,所以第一次添加的時候會將當(dāng)前elementData數(shù)組的長度變?yōu)?0
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //利用 == 可以判斷數(shù)組是否是用默認(rèn)構(gòu)造函數(shù)初始化的
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private static final int DEFAULT_CAPACITY = 10;

ensureExplicitCapacity()

經(jīng)過前面的一頓操作,minCapacity 是

  • DEFAULT_CAPACITY(10)
    當(dāng)?shù)谝淮翁砑訒r
  • size + 1
    當(dāng)不是第一次添加時
private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //如果確定要擴(kuò)容,會修改modCount 
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

grow()

Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 舊容量的1.5倍
  //如果還不夠 ,那么就用 能容納的最小的數(shù)量。(add后的容量)
    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);
}

在指定位置添加一個元素--add(int index, E element)

Inserts the specified element at the specified position in this list.

  1. 確保有足夠的容量之后
  2. 使用System.arraycopy 將需要插入的位置(index)后面的元素統(tǒng)統(tǒng)往后移動一位
  3. 將新的數(shù)據(jù)內(nèi)容存放到數(shù)組的指定位置(index)上
public void add(int index, E element) {
    rangeCheckForAdd(index);
    // 進(jìn)行擴(kuò)容檢查
    ensureCapacityInternal(size + 1);  // Increments modCount!!
   // 對數(shù)組進(jìn)行復(fù)制處理,目的就是空出index的位置插入element,并將index后的元素位移一個位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
   // 將指定的index位置賦值為element
    elementData[index] = element;
    size++;
}

關(guān)于arraycopy()

public static void (Object src,int srcPos,
                    Object dest,int destPos,
                    int length)
參數(shù) 意義
src 源數(shù)組
srcPos 源數(shù)組要復(fù)制的起始位置
dest 目的數(shù)組
destPos 目的數(shù)組放置的起始位置
length 復(fù)制的長度
  • src和dest都必須是同類型或者可以進(jìn)行轉(zhuǎn)換類型的數(shù)組
  • 可以實現(xiàn)自己到自己復(fù)制

其中rangeCheckForAdd是為了防止越界:

private void rangeCheckForAdd(int index) {
     if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

增加一個集合--addAll(Collection<? extends E> c)

Appends all of the elements in the specified collection to the end of this list, in the order that they are returned by the specified collection's Iterator.

 public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();//將集合轉(zhuǎn)換為數(shù)組
    int numNew = a.length;
    //擴(kuò)容檢查
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //將c添加至list的數(shù)據(jù)尾部
    System.arraycopy(a, 0, elementData, size, numNew);
    //更新當(dāng)前容器大小
    size += numNew;
    return numNew != 0;
}

在指定位置,增加一個集合元素--addAll(int index, Collection<? extends E> c)

Inserts all of the elements in the specified collection into this list, starting at the specified position.

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //計算需要移動的長度(index之后的元素個數(shù))
    int numMoved = size - index;
    //將數(shù)組index后的元素向右移動numNum個位置,即空出第index到index+numNum的位置
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
    //將要插入的集合元素復(fù)制到數(shù)組空出的位置中
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

關(guān)于增 的總結(jié):
add、addAll。
先判斷是否越界,是否需要擴(kuò)容。
如果擴(kuò)容, 就復(fù)制數(shù)組。
然后設(shè)置對應(yīng)下標(biāo)元素值。

值得注意的是:
1 如果需要擴(kuò)容的話,默認(rèn)擴(kuò)容一半。如果擴(kuò)容一半不夠,就用目標(biāo)的size作為擴(kuò)容后的容量。
2 在擴(kuò)容成功后,會修改modCount

五. 刪除---remove

根據(jù)索引位置刪除元素--remove(int index)

Removes the element at the specified position in this list.

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);// 供返回
    int numMoved = size - index - 1;// 計算數(shù)組要復(fù)制的數(shù)量
   // 數(shù)組復(fù)制,就是將index之后的元素往前移動一個位置
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 因為刪除了一個元素,然后index后面的元素都向前移動了,所以最后一個就沒用了
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

根據(jù)元素內(nèi)容刪除,只刪除匹配的第一個--remove(Object o)

Removes the first occurrence of the specified element from this list, if it is present. If the list does not contain the element, it is unchanged.

循環(huán)遍歷所有對象,得到對象所在索引位置,然后調(diào)用fastRemove方法,執(zhí)行remove操作

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

fastRemove()

Private remove method that skips bounds checking and does not return the value removed.

定位到需要remove的元素索引,先將index后面的元素往前面移動一位(調(diào)用System.arraycooy實現(xiàn)),然后將最后一個元素置空。

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
}

數(shù)組越界檢查

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

六. 查找---get

查找指定位置上的元素

Returns the element at the specified position in this list.

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

七. 更新---set

將指定位置的元素更新為新元素

Replaces the element at the specified position in this list with the specified element.

  • 注意返回值是oldValue
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);// 記錄要更新位置的元素,供返回使用
    elementData[index] = element;
    return oldValue;
}

八. 判斷

包含--contains(Object o)

調(diào)用indexOf方法,遍歷數(shù)組中的每一個元素作對比,如果找到對于的元素,則返回true,沒有找到則返回false

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
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;
}

空--isEmpty()

public boolean isEmpty() {
    return size == 0;
}

九. ArrayList遍歷方式

ArrayList支持3種遍歷方式

  1. 通過迭代器遍歷。即通過Iterator去遍歷。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}
  1. 隨機(jī)訪問,通過索引值去遍歷。
    由于ArrayList實現(xiàn)了RandomAccess接口,它支持通過索引值去隨機(jī)訪問元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}
  1. for循環(huán)遍歷。如下:
Integer value = null;
for (Integer integ:list) {
    value = integ;
}

十. ArrayList和 Vector 的區(qū)別

  • 同步
    Vector 和 ArrayList 幾乎是完全相同的,唯一的區(qū)別在于 Vector 是同步的,因此開銷就比 ArrayList 要大,訪問速度更慢。最好使用 ArrayList 而不是 Vector,因為同步操作完全可以由程序員自己來控制;
  • 擴(kuò)容
    Vector 每次擴(kuò)容請求其大小的 2 倍空間,而 ArrayList 是 1.5 倍。
    Vector 的grow()擴(kuò)容部分
 int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                 capacityIncrement : oldCapacity);

十一. 小回顧

  • 添加元素時可能要擴(kuò)容(所以最好預(yù)判一下),刪除元素時不會減少容量(若希望減少容量,trimToSize()),刪除元素時,將刪除掉的位置元素置為null,下次gc就會回收這些元素所占的內(nèi)存空間
  • add(int index, E element):添加元素到數(shù)組中指定位置的時候,需要將該位置及其后邊所有的元素都整塊向后復(fù)制一位
  • get(int index):獲取指定位置上的元素時,可以通過索引直接獲取(O(1))
  • remove(Object o)需要遍歷數(shù)組
  • remove(int index)不需要遍歷數(shù)組,只需判斷index是否符合條件即可,效率比remove(Object o)高
  • contains(E)需要遍歷數(shù)組

參考文章
Java集合干貨系列-(一)ArrayList源碼解析
Java官方文檔
ArrayList源碼解析(JDK8)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容