java.util.ArrayList學習筆記


概述


繼承結構


這里寫圖片描述

基本功能


ArrayList是一種可變長列表,基於數組實現。在這個類中,實現了List接口中定義的所有的可選方法,並且對其中可以放入的元素也沒有限制。出了實現List接口中定義的方法外,本類還提供了用於控制內部數組大小的方法。在java中,List基本魚Vector等價,但是List並不保證線程安全,而Vector保證線程安全性。

在本類中,執行size, isEmpty, get, set, iterator, 和 listIterator等方法的時間復雜度為O(1);執行add方法的時間復雜度與放入元素的個數呈正相關關系,即,當放入n個元素時,時間復雜度為:O(n);其它的方法的時間復雜度則保持在線性關系,與LinkedList實現相比,其常量因子的較小。

每個ArrayList實例均包含一個容量參數。這個參數指定了實例中用於保存列表元素的數組大小。每個鏈表的容量一般大於每個鏈表中有效元素的個數。當元素被添加到列表中時,其對應的容量可能發生改變。數組的擴容操作的時間消耗並沒有計入添加元素的時間復雜度中。

一個應用可以通過調用ensureCapacity方法,在插入大量元素之前對列表進行擴容操作。這種做法可能降低漸進性擴容的次數(如:插入大量元素時,可能需要擴容3次,通過采用這種策略,可以將擴容次數降低至1次,從而達到提高執行效率的目的)。

ArrayList並不時線程安全的。也就是說,當有多個線程同步訪問同一個對象,且至少有一個線程對列表結構進行了修改(即:進行了添加,刪除等操作),我們則必須在調用的外層進行同步控制。實現這中策略的方法一般是在包含該列表對象的外層對象中進行同步控制。如果沒有這中類型的對象,則需要調用Collections.synchronizedList方法來進行同步控制。一般情況下,最好在創建列表時調用Collections.synchronizedList方法,從而避免對列表對象的非同步訪問,其實例代碼如下:

List list = Collections.synchronizedList(new ArrayList(...));

通過iterator或者listIterator方法返回的列表迭代器采用fail-fast機制,即:當列表對象在迭代器創建后,除調用該迭代器的add和remove方法外,發生結構性性改變時,迭代器將拋出一個同步修改異常ConcurrentModificationException。因此,當遇到對象的同步修改時,迭代器將會立刻失敗,而不是繼續進行可能失效的訪問。

注意:fail-fast機制並不能保證強同步性,即,不能保證在發生非同步並發修改時,列表的訪問的正確性。fail-fast機制會在盡量在在發生非同步修改時拋出ConcurrentModificationException。但是在編程時,不能基於該異常實現程序的正確性,這中機制應僅僅用於檢測潛在bug。

屬性介紹


private static final int DEFAULT_CAPACITY = 10

一個包含元素的列表的默認初始化長度;

private static final Object[] EMPTY_ELEMENTDATA = {};

由所有空列表對象共享的數組實例。

private transient Object[] elementData;

列表對象數據的保存數組,數組長度代表列表的容量,數組中有效元素的個數,代表數組的長度。任何一個滿足條件elementData == EMPTY_ELEMENTDATA空列表中數據數組長度在添加第一個新元素時,均會被擴展至DEFAULT_CAPACITY。

private int size;

當前列表的大小,即:列表中包含元素的個數;

方法介紹


構造方法


public ArrayList(int initialCapacity) {
	super();
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
	this.elementData = new Object[initialCapacity];
}

構造一個初始容量為initialCapacity的空列表。

public ArrayList() {
	super();
	this.elementData = EMPTY_ELEMENTDATA;
}

構造一個容量為DEFAULT_CAPACITY的空列表。在默認的構造方法中,將數據數組指向默認的共享空數組。

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)
		elementData = Arrays.copyOf(elementData, size, Object[].class);
}   

創建一個包含集合c中所有元素的列表。其中元素的順序與c中迭代器返回元素的順序相同。

其它方法介紹

public void trimToSize() {
	modCount++;
	if (size < elementData.length) {
		elementData = Arrays.copyOf(elementData, size);
	}
}

將當前列表的容量縮減至列表當前的長度。可以通過這個方法來降低列表對象占用的內存。

public void ensureCapacity(int minCapacity) {
	int minExpand = (elementData != EMPTY_ELEMENTDATA)
	
	// any size if real element table
	
	? 0
	
	// larger than default for empty table. It's already supposed to be
	// at default size.
	
	: DEFAULT_CAPACITY;
	
	if (minCapacity > minExpand) {
		ensureExplicitCapacity(minCapacity);
	}
}

用於對列表對象進行擴容,並保證列表至少可以容納minCapacity個元素。一般通過列表對象數組是否指向共享空數組來判斷來確定其初始容量。在此有一點兒疑惑,將在下個函數中進行說明。

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

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

這個函數的主要作用,我理解為:保證列表對象的值數組至少可以容納minCapacity 個元素,否則進行列表的擴容操作。我們看到函數ensureCapacity(int)保留一個對本方法的引用。在本方法中,進行擴容操作之前首先修改了modCount參數,用於指定列表對象被修改。但是我們看到,當當前列表中值數組的長度大於指定的最小長度時,不會調用擴容參數。由此可以看出,參數modCount指定的並不是列表發生修改的絕對次數,而是其可能發生修改的次數。

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

擴展列表對象的值數組,當指定的元素個數小於列表默認的容量時,則將列表值數組擴展至默認容量,否則擴展至:minCapacity

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

對列表進行擴容操作,使之至少可以容納minCapacity個元素。其基本思想是:首先將列表中的值數組擴展到原來的1.5倍,如果比預期的最小容量minCapacity 小,則直接將擴容至minCapacity 。如果擴容后的容量大於列表允許的最大容量,則擴容至Integer.MAX_VALUE,否則擴容至MAX_ARRAY_SIZE

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

當擴容后的預期容量超出列表規定的最大容量時,則調用本函數,確定列表最后完成擴容操作后的容量。

public int size()

返回當前列表中元素的個數。

public boolean isEmpty()

判斷列表中是否包含元素。

public boolean contains(Object o)

判斷列表中是否包含某個元素。當列表至少包含一個與o相等的對象時,返回true。其內部實現采用的方式為:確定對象在列表中第一出現位置的下標是否為-1;

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

返回對象在列表中第一次出現位置的下標。當對象在列表中不存在時,則返回-1。其內部采用的實現方式為:遍歷列表值數組中前size個元素(這里,我理解為有效元素),返回第一個與指定對象相等的元素下標。

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

返回對象在列表中最后一次出現位置的下標。采用逆向遍歷列表值數組中元素的方式。

public Object clone() {
	try {
		@SuppressWarnings("unchecked")
		ArrayList<E> v = (ArrayList<E>) 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();
	}
}

克隆一個新的列表對象。注意:復制后的列表對象的修改次數為0,雖然復制了列表中的值數組對象,但是值數組對象中包含的所有元素並沒有復制。

public Object[] toArray()

將列表中的元素轉化為數組,其內部實現基於Arrays.copyOf()方法,復制后的數組中元素的順序與列表對象中的值數組中的元素順序相同。

public <T> T[] toArray(T[] a)

基於Arrays.copyOfSystem.arraycopy實現。其接口說明參見:java.util.List學習筆記

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

返回當前列表對象值數組中下標為index的元素,並轉化為列表對象聲明時的對象類型。

public E get(int index)

獲取列表中下標為index的元素。本方法基於elementData(int)實現,並具有訪問下標的合法性檢驗,確保其訪問的元素的有效性。

private void rangeCheck(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

校驗訪問元素下標有效性。但是,方法中只校驗了上限,但是沒有校驗下限,因此在使用時要注意。

public E set(int index, E element) {
	rangeCheck(index);

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

將列表中下標為index的元素替代為指定元素,並將該位置原元素返回。由於在替換操作中,需要訪問元素存在,因此在替換之前,需要執行訪問元素下標校驗。

public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

將一個元素追加到列表的末尾。該方法中需要注意以下幾點:插入元素前,需要確保列表容量至少為size + 1。

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

在列表值數組下標為index的位置加入一個新元素,其后的元素依次后移。其采用的方式為:首先檢測插入下標是否合法,然后進行列表的擴容操作,基於System.arraycopy實現數組元素的后移,最后將index位置的元素設定為指定元素。

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

刪除列表下標為index的位置的元素,並將其返回,其后的元素依次前移。其采用的方式為:首先檢測刪除下標是否合法,基於System.arraycopy實現數組元素的前移,最后將列表值數組的實效元素設定為null,由GC負責回收。

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
}

針對列表特定位置的元素的快速刪除算法,算法原理基本與刪除特定位置的元素的方法相同,但是這個算法並不進行邊界的校驗。

public boolean remove(Object o)

刪除列表中第一個與指定對象相等的元素,其判斷元素相等的算法與contains(object)基本相同。

public void clear() {
	modCount++;

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

	size = 0;
}

刪除列表中的所有元素。其內部實現采用簡單的將列表值數組中所有的元素設成null實現,並將列表的大小設定為0。但是並不修改列表的容量。經過前面這些方法源碼的閱讀,可以確定modCount並不是指列表發生所有改變的次數,而是列表可能發生的結構性改變的次數。

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

將集合c中所有的元素追加到列表的末尾,其添加順序與集合c的迭代器返回其中元素的順序相同。添加原理與添加單個元素基本相同,只是在申請空間時,將預期的容量設定成了:size + numNew。

public boolean addAll(int index, Collection<? extends E> c)

將集合c中的所有元素添加到列表的指定位置。其實現原理與添加單個元素基本相同,詳情可以參見add(Object)方法的說明。

protected void removeRange(int fromIndex, int toIndex)

刪除列表指定范圍內的元素。其區間范圍是fromIndex <= indedx < toIndex。其實現的基本原理與刪除單個元素基本相同,只是在數組的元素的移動時的下標計算不同,詳細說明可以參見:remove(int)方法。

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

判斷新增元素的下標是否盒飯,當index=size時,則代表向列表末尾處添加一個新元素。

private String outOfBoundsMsg(int index)

生成IndexOutOfBoundsException異常的詳細信息,指明訪問列表的非法下標和當前列表的有效元素個數。

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++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            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;
    }

批量刪除列表中的元素。當complement為true時,則取列表與c的交集,否則取差集。其基本思想為:指定兩個游標,分別標記讀坐標和寫坐標,遍歷列表中的元素,每次訪問,讀坐標加1,依次判定是否滿足指定條件,當滿足時,將寫坐標當前指向的元素替代為讀坐標當前指向的元素。最后根據讀、寫坐標。在執行本操作過程中,如果contains拋出異常,則由拋出異常時的讀坐標開始,將其復制到寫數組的末尾,並對寫坐標進行修改。最后將寫坐標后的元素全部指定為null,並進行列表結構性修改的次數。

public boolean removeAll(Collection<?> c)

取列表與c的交集,並將其保存到列表中。

public boolean retainAll(Collection<?> c)

取列表與c的差集,並將其保存到列表中。

public ListIterator<E> listIterator(int index) {
	if (index < 0 || index > size)
		throw new IndexOutOfBoundsException("Index: "+index);
	return new ListItr(index);
}

獲取由指定位置開始的列表迭代器。即,用戶第一次調用迭代器的next()方法返回的是列表中下標為index的元素。但是迭代器的調用方仍然可以使用本迭代器進行列表中元素的遍歷。在這里需要特別注意列表迭代器游標的有效位置為:0 ~ size。當 index = size時,則代表列表迭代器的初始游標在列表的末尾,其next()方法將返回null或者拋出異常,一般可以用於逆向遍歷列表。

public ListIterator<E> listIterator()

返回一個從列表頭部開始的列表迭代器,其內部實現采用的是:listIterator(0)

public Iterator<E> iterator()

返回一個從列表頭部開始的基本迭代器。

public List<E> subList(int fromIndex, int toIndex)

返回當前列表的一個子列表。其中元素共享,即:在子列表中發生的修改會直接反映到初始列表中,反之亦然。

私有內部類


基礎迭代器(Itr)


參數介紹


int cursor

迭代器當前的游標。迭代器的游標代表的為:當調用next()方法時,返回的列表元素的下標。

int lastRet = -1

最后一次調用next()方法返回的元素的下標。默認為-1。

int expectedModCount = modCount

迭代器創建時,列表對象瞬態的可能的結構變化次數,主要用於檢測是否存在並發修改,從而導致並發錯誤。

方法介紹


public boolean hasNext()

判斷當前迭代器調用next()方法是否還有有效元素返回。當列表的游標已經達到列表末尾時,即:cursor == size時候,則返回false,否則返回true。

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

返回當前迭代器正向遍歷的下一個元素。注意,本算法中有兩次並發結構修改檢測,分別是,強校驗:方法開始時,調用checkForComodification進行結構性修改次數檢測;弱校驗:如果當前游標的位置高於列表的容量,產生情形為:列表發生元素的刪除操作,並調用了trimToSize來降低列表的空間占用。在成功完成校驗后,需要交迭代器游標后移,並將最后一次訪問的元素的坐標設定為訪問元素的下標。

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

刪除列表迭代器剛剛訪問的元素。當迭代器並沒有訪問任何元素,或者剛剛訪問的元素已經被刪除時,其內部基於ArrayList.this.remove()實現。在完成刪除后,需要將迭代器游標前移,將最后一次訪問元素的下標置為-1,並對迭代器中結構性修改次數進行更新。

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

檢測除持有本迭代器的線程外,是否有潛在的並發修改沖突。其基本原理是檢測列表對象的

列表迭代器(ListItr)


繼承結構


這里寫圖片描述

方法介紹


具體方法說明,請參見:java.util.ListIterator學習筆記
其內部基本基於ArrayList中的方法實現。在進行元素訪問操作時,均會進行同步修改校驗。

子列表(SubList)


構造方法

SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
	this.parent = parent;
	this.parentOffset = fromIndex;
	this.offset = offset + fromIndex;
	this.size = toIndex - fromIndex;
	this.modCount = ArrayList.this.modCount;
}

由構造方法,我們可以看出:子列表持有的並不是父親列表中元素的復制,而是直接維持一個父親列表的引用,因此子列表對其中元素的修改會直接影響到父親列表,甚至是根列表,反之亦然。並且,子列表的結構修改次數是與根列表的相同,用於檢測並發的修改沖突。

基本屬性


private final AbstractList<E> parent;

代表當前子列表的直接父親列表。即:列表3是列表2的子列表,列表2為列表1的子列表,則在列表3中,parent指向的是列表2。

private final int parentOffset;

當前子列表相對於直接父親列表的元素偏移量。即:列表3相對於列表2的元素偏移量。

private final int offset;

當前子列表相對於根列表的元素偏移量,即:列表3相對於列表1的元素偏移量。

int size;

當前子列表的長度。

注:對於子列表的接口說明,可以參見java.util.AbstractList學習筆記。在ArrayList的實現中,通過數組的下標和數組的copyOf函數實現subList的所有功能。其實現的邏輯與ArrayList本身的實現邏輯大體相同,在此不做贅述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM