ArrayList源碼分析

ArrayList

原文見:Java 容器源碼分析之 ArrayList

概述

ArrayList是使用頻率最高的集合之一了,在需要使用List的情況下,往往都是優(yōu)先考慮ArrayList。首先我們來看一下聲明:

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

ArrayList實(shí)現(xiàn)的幾個(gè)接口中,RandomAccess、Cloneable、Serializable都是標(biāo)記接口,所以ArrayList是很純粹的List接口的實(shí)現(xiàn),不像它兄弟LinkedList還實(shí)現(xiàn)了Deque接口,還要作為雙向隊(duì)列使用。

結(jié)構(gòu)

transient Object[] elementData;

// 這個(gè)繼承自父類AbstractList
protected transient int modCount = 0;

ArrayList的名稱中我們就可以看出來,這是一個(gè)用數(shù)組實(shí)現(xiàn)的List,或者說是可變數(shù)組,數(shù)據(jù)就是存儲(chǔ)在elementData這個(gè)對(duì)象數(shù)組里。除了elementData我們還需要關(guān)注一個(gè)重要的成員變量modCountmodCount成員變量是繼承自父類AbstractListmodCount表示這個(gè)List被結(jié)構(gòu)化修改的次數(shù),結(jié)構(gòu)化修改就是那些會(huì)改變List的大小的操作。modCount主要被用在迭代器上,如果一個(gè)List在迭代的過程中發(fā)生了結(jié)構(gòu)化修改,就會(huì)導(dǎo)致結(jié)果出錯(cuò)。在List迭代過程中,如果因?yàn)槠渌€程對(duì)List的操作,導(dǎo)致結(jié)構(gòu)發(fā)生變化,那么迭代器就拋出ConcurrentModificationException,這就是迭代器的fail-fast機(jī)制。

添加元素

/**
 * Appends the specified element to the end of this list.
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 */
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++;
}

/**
 * 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.  The behavior of this operation is
 * undefined if the specified collection is modified while the operation
 * is in progress.  (This implies that the behavior of this call is
 * undefined if the specified collection is this list, and this
 * list is nonempty.)
 */
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;
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in the list in the order that they are returned by the
 * specified collection's iterator.
 */
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 rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

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

有多個(gè)方法來給ArrayList添加元素,add(E e)是添加到數(shù)組末尾,add(int index, E element)是添加到指定位置,addAll(Collection<? extends E> c)批量添加元素到數(shù)組末尾,addAll(int index, Collection<? extends E> c)批量添加元素到指定位置。

本質(zhì)上這幾個(gè)方法都是相同的,首先通過rangeCheck或者rangeCheckForAdd方法判斷index是否合法。然后通過ensureCapacityInternal方法來確保數(shù)組的容量足夠,該方法會(huì)先判斷當(dāng)前數(shù)組容量是否足夠,如果不夠就進(jìn)行擴(kuò)容,待會(huì)會(huì)進(jìn)行介紹。不過需要注意的是,添加元素是會(huì)造成ArrayList結(jié)構(gòu)化改變的,所以modCount的值要增加。而源碼中將modCount自增操作放在了ensureCapacityInternal方法里,感覺有點(diǎn)怪怪的,從方法的命名中可以看出這個(gè)方法是用來確保數(shù)組容量的,但是卻在這個(gè)方法里修改了與方法容量無關(guān)的成員變量,所以我覺得設(shè)計(jì)得不是很合理。寫代碼的人也覺得自己這樣搞不是很合理,所以才通過注釋來說明。

ensureCapacityInternal(size + 1); // Increments modCount!!

接著剛才的話題,當(dāng)確保數(shù)組的容量足夠之后,再通過靜態(tài)方法System.arraycopy()將元素拷貝到合適的位置,對(duì)原數(shù)組進(jìn)行重新排序就可以了。當(dāng)然,添加到末尾就不用考慮到數(shù)組重排序的問題了,直接將待添加元素放到末尾就可以了。最后修改size到相應(yīng)的數(shù)值,添加元素的操作就完成了。

擴(kuò)容

ArrayList是基于可變數(shù)組的,當(dāng)?shù)讓訑?shù)組容量不足時(shí)會(huì)進(jìn)行擴(kuò)容,以改變數(shù)組的容量。代碼如下:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * 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) {
    // 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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

前面那些ensure開頭的方法是用來檢測(cè)當(dāng)前數(shù)組容量是否足夠容納minCapacity的,如果容量不足才會(huì)進(jìn)行擴(kuò)容,即調(diào)用grow(int capacity)方法,我們直接來看grow()方法。

grow()方法首先將數(shù)組容量擴(kuò)張為原來的1.5倍,即int newCapacity = oldCapacity + (oldCapacity >> 1)這條語句。然后再判斷新容量是否滿足最小所需容量minCapacity,如果還是不能滿足,就將newCapacity設(shè)置為minCapacity。接下來要判斷newCapacity是否超過了最大允許的數(shù)組大小MAX_ARRAY_SIZE,如果超過了就調(diào)整為最大的int值。最后就是將原數(shù)組的值拷貝到新的數(shù)組上。

移除元素

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 */
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;
}

/**
 * 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.  More formally, removes the element with the lowest index
 */
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;
}

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
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
}

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;

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

    size = 0;
}

/**
 * Removes from this list all of the elements whose index is between
 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
 * Shifts any succeeding elements to the left (reduces their index).
 * This call shortens the list by {@code (toIndex - fromIndex)} elements.
 * (If {@code toIndex==fromIndex}, this operation has no effect.)
 */
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

其實(shí)移除元素的原理很簡(jiǎn)單,就是通過System.arraycopy方法將需要保留的元素復(fù)制到正確的位置上,然后調(diào)整size的大小。最后為了防止內(nèi)存泄露,需要顯式將不再使用的位置中存放的元素置為null。雖然原理簡(jiǎn)單,但是需要注意的細(xì)節(jié)很多,大多是索引值方面的小細(xì)節(jié)。

接下來看一下批量刪除或者保留元素的方法。

/**
 * Removes from this list all of its elements that are contained in the
 * specified collection.
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

/**
 * Retains only the elements in this list that are contained in the
 * specified collection.  In other words, removes from this list all
 * of its elements that are not contained in the specified collection.
 */
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            //1) 移除c中元素,complement == false
            //   若elementData[r]不在c中,則保留
            //2)保留c中元素,complement == true
            //   若elementData[r]在c中,則保留
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        // 1)r == size, 則操作成功了
        // 2)r != size, c.contains拋出了異常,
        //      可能是因?yàn)樵睾蚦中元素類型不兼容,或者c不支持null元素
        //      則將后面尚未檢查的元素向前復(fù)制
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // 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;
}

其中,無論是批量移除removeAll()方法還是批量保留retainAll()方法,都是使用了batchRemove方法,我們直接來看這個(gè)方法。

先來說一下原理,首先通過便利整個(gè)數(shù)組,找出需要保留的元素,從索引0開始依次保存到elementData數(shù)組中。如果便利過程沒有異常出現(xiàn)(也就是r==size),則顯式將不再使用的位置中存放的元素置為null,讓GC回收。當(dāng)然如果便利過程出現(xiàn)異常(r!=size),則要將未被便利的值拷貝到w索引及之后的位置。暫時(shí)不清楚對(duì)異常的處理是否合理。

查找與更新

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
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;
}

/**
 * Returns the index of the last occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the highest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

/**
 * Returns the element at the specified position in this list.
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 */
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

因?yàn)槭腔跀?shù)組實(shí)現(xiàn)的,所以查找元素和更新元素比較簡(jiǎn)單。這幾個(gè)方法都沒有改變List的結(jié)構(gòu),所以不會(huì)修改modCount的值。

迭代

列表的迭代也是開發(fā)中經(jīng)常使用到了,特別是使用for each語句進(jìn)行迭代。因?yàn)?code>Collection接口繼承了Iterable接口,ArrayList間接實(shí)現(xiàn)了Collection,所以需要實(shí)現(xiàn)Iterable接口的iterator()方法,下面我們來看一下。

public Iterator<E> iterator() {
    return new Itr();
}
/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

迭代器中通過cursor來標(biāo)注下一個(gè)待返回元素的索引值,還有一個(gè)lastRet來標(biāo)注上一個(gè)被返回元素的索引值。ArrayList的實(shí)現(xiàn)不是線程安全的,其fail-fast機(jī)制的實(shí)現(xiàn)是通過modCount變量來實(shí)現(xiàn)的。在nextremove里都有checkForComodification()的方法,在該方法中,會(huì)比較Iterator創(chuàng)建時(shí)的modCount(expectedModCount)和當(dāng)前的modCount的值是否相等。不過不相,證明在迭代器創(chuàng)建之后ArrayList的結(jié)構(gòu)有被修改過,此時(shí)拋出ConcurrentModificationException異常。

需要注意的一點(diǎn)在于,remove()方法調(diào)用時(shí),會(huì)判斷lastRet < 0,如果小于0,就會(huì)拋出異常。出現(xiàn)lastRet<0只有兩種情況,一種是剛創(chuàng)建迭代器,還未調(diào)用next()方法的時(shí)候,一種是調(diào)用過一次remove()方法后會(huì)把lastRet設(shè)置為-1。所以連續(xù)兩次調(diào)用remove()方法是會(huì)拋出異常的。

List接口還支持另一種迭代器ListIterator,它不僅可以使用next()向前迭代,還可以使用previous()向后迭代;不僅可以使用remove()在迭代中移除元素,還可以使用add()方法在迭代中添加元素。

小結(jié)

ArrayList內(nèi)部使用數(shù)組實(shí)現(xiàn),具有高效的隨機(jī)訪問的特性。但是插入和刪除元素時(shí)往往需要復(fù)制數(shù)組,開銷較大。在容器創(chuàng)建之后需要進(jìn)行大量訪問,但插入和刪除操作使用較少的情況下比較適合使用ArrayList。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,945評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • ArrayList是在Java中最常用的集合之一,其本質(zhì)上可以當(dāng)做是一個(gè)可擴(kuò)容的數(shù)組,可以添加重復(fù)的數(shù)據(jù),也支持隨...
    ShawnIsACoder閱讀 576評(píng)論 4 7
  • 每個(gè) ArrayList 實(shí)例都有一個(gè)容量,該容量是指用來存儲(chǔ)列表元素的數(shù)組的大小。它總是至少等于列表的大小。隨著...
    Mervyn_2014閱讀 203評(píng)論 0 0
  • 定義 除了實(shí)現(xiàn)了List接口,還實(shí)現(xiàn)了RandomAccess,Cloneable, java.io.Serial...
    zhanglbjames閱讀 434評(píng)論 0 0
  • List List是一個(gè)維持內(nèi)部元素有序的采集器,其中的每個(gè)元素都會(huì)擁有一個(gè)索引,每個(gè)元素都可以通過他的索引獲取到...
    dooze閱讀 408評(píng)論 0 4
  • 整體介紹 ArrayList實(shí)現(xiàn)了List接口,是一個(gè)常見的集合類,它有一下特點(diǎn): 是順序容器,即元素存放的數(shù)據(jù)與...
    SeaRise閱讀 352評(píng)論 0 0