標籤:-- int 應用 sdn void math 注釋 throws blog
轉自:76760238
頂部注釋
List介面的大小可變數組的實現。實現了所有可選列表操作,並允許包括null在內的所有元素。除了實現List介面外,此類還提供一些方法來操作內部用來儲存列表的數組的大小。(此類大致上等同於Vector類,除了此類是不同步的。)
size、isEmpty、get、set、iterator和listIterator操作都以固定時間運行。add操作以分攤的固定時間運行,也就是說,添加n個元素需要O(n)時間。其他所有操作都以線性時間運行(大體上講)。與用於LinkedList實現的常數因子相比,此實現的常數因子較低。
每個ArrayList執行個體都有一個容量。該容量是指用來儲存列表元素的數組的大小。它總是至少等於列表的大小。隨著向ArrayList中不斷添加元素,其容量也自動成長。並未指定增長策略的細節,因為這不只是添加元素會帶來分攤固定時間開銷那樣簡單。
在添加大量元素前,應用程式可以使用ensureCapacity操作來增加ArrayList執行個體的容量。這可以減少遞增式再分配的數量。
注意,此實現不是同步的。如果多個線程同時訪問一個ArrayList執行個體,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。(結構上的修改是指任何添加或刪除一個或多個元素的操作,或者顯式調整底層數組的大小;僅僅設定元素的值不是結構上的修改。)這一般通過對自然封裝該列表的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用Collections.synchronizedList方法將該列表“封裝”起來。這最好在建立時完成,以防止意外對列表進行不同步的訪問:
List list = Collections.synchronizedList(new ArrayList(…));
此類的iterator和listIterator方法返回的迭代器是快速失敗的:在建立迭代器之後,除非通過迭代器自身的remove或add方法從結構上對列表進行修改,否則在任何時間以任何方式對列表進行修改,迭代器都會拋出ConcurrentModificationException。因此,面對並發的修改,迭代器很快就會完全失敗,而不是冒著在將來某個不確定時間發生任意不確定行為的風險。
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步並發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤的做法:迭代器的快速失敗行為應該僅用於檢測bug。
此類是Java Collections Framework的成員。
從上面的內容中可以總結出以下幾點:
- 底層:ArrayList是List介面的大小可變數組的實現。
- 是否允許null:ArrayList允許null元素。
- 時間複雜度:size、isEmpty、get、set、iterator和listIterator方法都以固定時間運行,時間複雜度為O(1)。add和remove方法需要O(n)時間。與用於LinkedList實現的常數因子相比,此實現的常數因子較低。
- 容量:ArrayList的容量可以自動成長。
- 是否同步:ArrayList不是同步的。
- 迭代器:ArrayList的iterator和listIterator方法返回的迭代器是fail-fast的。
定義
先來看看ArrayList的定義:
public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,java.io.Serializable
從中我們可以瞭解到:
- ArrayList<E>:說明ArrayList支援泛型。
- extends AbstractList<E> :繼承了AbstractList。AbstractList提供List介面的骨幹實現,以最大限度地減少“隨機訪問”資料存放區(如ArrayList)實現Llist所需的工作。
- implements List<E>:實現了List。實現了所有可選列表操作。
- implements RandomAccess:表明ArrayList支援快速(通常是固定時間)隨機訪問。此介面的主要目的是允許一般的演算法更改其行為,從而在將其應用到隨機或連續訪問列表時能提供良好的效能。
- implements Cloneable:表明其可以調用clone()方法來返回執行個體的field-for-field拷貝。
- implements java.io.Serializable:表明該類具有序列化功能。
域
/** * 初始化預設容量。 */private static final int DEFAULT_CAPACITY = 10;/** * 指定該ArrayList容量為0時,返回該空數組。 */private static final Object[] EMPTY_ELEMENTDATA = {};/** * 當調用無參構造方法,返回的是該數組。剛建立一個ArrayList 時,其內資料量為0。 * 它與EMPTY_ELEMENTDATA的區別就是:該數組是預設返回的,而後者是在使用者指定容量為0時返回。 */private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/** * 儲存添加到ArrayList中的元素。 * ArrayList的容量就是該數組的長度。 * 該值為DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時,當第一次添加元素進入ArrayList中時,數組將擴容值DEFAULT_CAPACITY。 * 被標記為transient,在對象被序列化的時候不會被序列化。 */transient Object[] elementData; // non-private to simplify nested class access/** * ArrayList的實際大小(數組包含的元素個數)。 * @serial */private int size;
構造方法
接下來,看ArrayList提供的構造方法。ArrayList提供了三種構造方法。
- ArrayList(int initialCapacity):構造一個指定容量為capacity的空ArrayList。
- ArrayList():構造一個初始容量為 10 的空列表。
- ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。
ArrayList( int initialCapacity)
/** * 構造一個指定初始化容量為capacity的空ArrayList。 * * @param initialCapacity ArrayList的指定初始化容量 * @throws IllegalArgumentException 如果ArrayList的指定初始化容量為負。 */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); }}
ArrayList()
/** * 構造一個初始容量為 10 的空列表。 */public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
ArrayList(Collection<? extends E> c)
/** * 構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。 * * @param c 其元素將放置在此列表中的 collection * @throws NullPointerException 如果指定的 collection 為 null */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有以下核心方法
| 方法名 |
時間複雜度 |
| get(int index) |
O(1) |
| add(E e) |
O(1) |
| add(add(int index, E element)) |
O(n) |
| remove(int index) |
O(n) |
| set(int index, E element) |
O(1) |
get( int index)
/** * 返回list中索引為index的元素 * * @param index 需要返回的元素的索引 * @return list中索引為index的元素 * @throws IndexOutOfBoundsException 如果索引超出size */public E get(int index) { //越界檢查 rangeCheck(index); //返回索引為index的元素 return elementData(index);}/** * 越界檢查。 * 檢查給出的索引index是否越界。 * 如果越界,拋出運行時異常。 * 這個方法並不檢查index是否合法。比如是否為負數。 * 如果給出的索引index>=size,拋出一個越界異常 */private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}/** * 返回索引為index的元素 */@SuppressWarnings("unchecked")E elementData(int index) { return (E) elementData[index];}
從代碼中可以看到,因為ArrayList底層是數組,所以它的get方法非常簡單,先是判斷一下有沒有越界,之後就直接通過數組下標來擷取元素。get方法的時間複雜度是O(1)。
add(E e)
/** * 添加元素到list末尾. * * @param e 被添加的元素 * @return true */public boolean add(E e) { //確認list容量,如果不夠,容量加1。注意:只加1,保證資源不被浪費 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true;}
擴容-ensureCapacity等方法
加ArrayList容量。* * @param minCapacity 想要的最小容量*/public void ensureCapacity(int minCapacity) { // 如果elementData等於DEFAULTCAPACITY_EMPTY_ELEMENTDATA,最小擴容量為DEFAULT_CAPACITY,否則為0 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0: DEFAULT_CAPACITY; //如果想要的最小容量大於最小擴容量,則使用想要的最小容量。 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); }}/*** 數組容量檢查,不夠時則進行擴容,只供類內部使用。* * @param minCapacity 想要的最小容量*/private void ensureCapacityInternal(int minCapacity) { // 若elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,則取minCapacity為DEFAULT_CAPACITY和參數minCapacity之間的最大值 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);}/*** 數組容量檢查,不夠時則進行擴容,只供類內部使用* * @param minCapacity 想要的最小容量*/private void ensureExplicitCapacity(int minCapacity) { modCount++; // 確保指定的最小容量 > 數組緩衝區當前的長度 if (minCapacity - elementData.length > 0) //擴容 grow(minCapacity);}/** * 指派給arrays的最大容量 * 為什麼要減去8呢? * 因為某些VM會在數組中保留一些頭字,嘗試分配這個最大儲存容量,可能會導致array容量大於VM的limit,最終導致OutOfMemoryError。 */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** 擴容,保證ArrayList至少能儲存minCapacity個元素* 第一次擴容,邏輯為newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增加一半。第一次擴容後,如果容量還是小於minCapacity,就將容量擴充為minCapacity。* * @param minCapacity 想要的最小容量*/private void grow(int minCapacity) { // 擷取當前數組的容量 int oldCapacity = elementData.length; // 擴容。新的容量=當前容量+當前容量/2.即將當前容量增加一半。 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) { //如果minCapacity<0,拋出異常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //如果想要的容量大於MAX_ARRAY_SIZE,則分配Integer.MAX_VALUE,否則分配MAX_ARRAY_SIZE return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
看完了代碼,可以對擴容方法總結如下:
- 進行空間檢查,決定是否進行擴容,以及確定最少需要的容量
- 如果確定擴容,就執行
grow(int minCapacity),minCapacity為最少需要的容量
- 第一次擴容,邏輯為
newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增加一半。
- 第一次擴容後,如果容量還是小於minCapacity,就將容量擴充為minCapacity。
- 對擴容後的容量進行判斷,如果大於允許的最大容量MAX_ARRAY_SIZE,則將容量再次調整為MAX_ARRAY_SIZE。至此擴容操作結束。
add( int index, E element)
/** * 在制定位置插入元素。當前位置的元素和index之後的元素向後移一位 * * @param index 即將插入元素的位置 * @param element 即將插入的元素 * @throws IndexOutOfBoundsException 如果索引超出size */public void add(int index, E element) { //越界檢查 rangeCheckForAdd(index); //確認list容量,如果不夠,容量加1。注意:只加1,保證資源不被浪費 ensureCapacityInternal(size + 1); // Increments modCount!! // 對數組進行複製處理,目的就是空出index的位置插入element,並將index後的元素位移一個位置 System.arraycopy(elementData, index, elementData, index + 1,size - index); //將指定的index位置賦值為element elementData[index] = element; //實際容量+1 size++;}
從源碼中可以看到,add(E e)有三個步驟:
- 越界檢查
- 空間檢查,如果有需要進行擴容
- 插入元素
越界檢查很簡單
private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}
add(int index, E e)需要先對元素進行移動,然後完成插入操作,也就意味著該方法有著線性時間複雜度,即O(n)。
remove( int index)
/** * 刪除list中位置為指定索引index的元素 * 索引之後的元素向左移一位 * * @param index 被刪除元素的索引 * @return 被刪除的元素 * @throws IndexOutOfBoundsException 如果參數指定索引index>=size,拋出一個越界異常 */public E remove(int index) { //檢查索引是否越界。如果參數指定索引index>=size,拋出一個越界異常 rangeCheck(index); //結構性修改次數+1 modCount++; //記錄索引為inde處的元素 E oldValue = elementData(index); // 刪除指定元素後,需要左移的元素個數 int numMoved = size - index - 1; //如果有需要左移的元素,就移動(移動後,該刪除的元素就已經被覆蓋了) if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // size減一,然後將索引為size-1處的元素置為null。為了讓GC起作用,必須顯式的為最後一個位置賦null值 elementData[--size] = null; // clear to let GC do its work //返回被刪除的元素 return oldValue;}/** * 越界檢查。 * 檢查給出的索引index是否越界。 * 如果越界,拋出運行時異常。 * 這個方法並不檢查index是否合法。比如是否為負數。 * 如果給出的索引index>=size,拋出一個越界異常 */private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}
看完代碼後,可以將ArrayList刪除指定索引的元素的步驟總結為
- 檢查索引是否越界。如果參數指定索引index>=size,拋出一個越界異常
- 將索引大於index的元素左移一位(左移後,該刪除的元素就被覆蓋了,相當於被刪除了)。
- 將索引為size-1處的元素置為null(為了讓GC起作用)。
注意:為了讓GC起作用,必須顯式的為最後一個位置賦null值。上面代碼中如果不手動賦null值,除非對應的位置被其他元素覆蓋,否則原來的對象就一直不會被回收。
set( int index, E element)
/** * 替換指定索引的元素 * * @param 被替換元素的索引 * @param element 即將替換到指定索引的元素 * @return 返回被替換的元素 * @throws IndexOutOfBoundsException 如果參數指定索引index>=size,拋出一個越界異常 */public E set(int index, E element) { //檢查索引是否越界。如果參數指定索引index>=size,拋出一個越界異常 rangeCheck(index); //記錄被替換的元素 E oldValue = elementData(index); //替換元素 elementData[index] = element; //返回被替換的元素 return oldValue;}
Java8源碼-ArrayList