Java | ArrayList

Java8中ArrayList是基於數組保存數據,相當於一個數組隊列,但該數組是動態數組,即數組大小可以被擴容。

接下來看下ArrayList類繼承關係(IDEA中快捷鍵Ctrl + Alt + U查看),如下:

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

如上圖所示:

ArrayList 繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable四類接口。

其中AbstractList實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
ArrayList 實現了RandmoAccess接口,爲List提供隨機快速訪問功能,即可以通過元素的序號快速獲取元素對象;
ArrayList 實現了Cloneable接口,表示能被克隆;
ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能通過序列化去傳輸;

ArrayList總結:
ArrayList實際上是通過一個數組來保存數據。若使用默認構造函數,則ArrayList的默認容量大小是10。
ArrayList容量不足以容納全部元素時,ArrayList會重新設置容量:新的容量="(原始容量x3)/2 + 1"。
ArrayList的克隆函數,即是將全部元素克隆到一個數組中。
ArrayList實現java.io.Serializable的方式。當寫入到輸出流時,先寫入"容量",再依次寫入"每一個元素";當讀出輸入流時,先讀取"容量",再依次讀取"每一個元素"。


接下來看下Java8 中ArrayList的具體實現:

先看下ArrayList內定義的全局變量有什麼作用

// 默認初始化容量
private static final int DEFAULT_CAPACITY = 10;

// 用於空實例的共享空數組實例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用於默認大小的空實例的共享空數組實例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存儲ArrayList元素的數組緩衝區。ArrayList的容量是此數組緩衝區的長度。
// 任何elementData==DEFAULTCAPACITY的空ArrayList 將在添加第一個元素時擴展到默認容量。
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList包含的元素數量大小
private int size;

 // 最大數組容量大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

再來看下ArrayList的構造方法:

 // 構造一個空list且容量大小爲指定的initialCapacity值。
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);
	}
}

 // 使用默認容量的空數組常亮完成初始化 即構造一個空list且初始化容量大小爲10。
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 構造包含指定的集合,按集合返回它們的順序迭代器。
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()再來看看ArrayList內添加元素的具體過程:

 // 追加一個指定的元素到list尾部
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

 // 在list中指定位置插入指定元素,並將當前位於該位置的元素和任何後續元素右移一位。
public void add(int index, E element) {
	// 判斷index索引值是否滿足條件,即 0 <= index <= size
	rangeCheckForAdd(index);
	// 數組容量自增 
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	// 將elementData中index後續元素往後挪一位
	System.arraycopy(elementData, index, elementData, index + 1, size - index);
	elementData[index] = element;
	size++;
}

 // 將指定的元素集合內所有元素追加到list尾部
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;
}

// 將指定的元素集合內所有元素追加到指定的index索引位置,並將該index及後續元素右移。
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)
		//將原數組中需要移動的元素往後移動numNew位
		System.arraycopy(elementData, index, elementData, index + numNew,
						 numMoved);
	// 如果不需要移動原數組元素,則直接將目標數組a中的元素複製追加到源數組尾部
	System.arraycopy(a, 0, elementData, index, numNew);
	size += numNew;
	return numNew != 0;
}

當ArrayList列表中添加元素時,需要先對當前數組容量進行擴容,然後在添加元素,回過頭再來看下數組擴容的具體過程:

// 明確容量初始值並確認是否需要擴容
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 計算最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

// 明確容量大小
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	// 擴容長度大於當前數組長度大小則擴容
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

 // 增加容量以確保它至少可以容納最小容量參數指定的元素數。
private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	// 默認擴容當前數組大小的50%,即1.5倍
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	// 擴容後大小與minCapacity比較,小於minCapacity則以minCapacity爲準
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	// 若擴容後大小大於MAX_ARRAY_SIZE
	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;
}

接下來再來看下ArrayList中獲取元素的過程,如下:

 // 返回列表中指定索引位置的元素
public E get(int index) {
	rangeCheck(index);

	return elementData(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;
}

 // 倒序遍歷數組查找指定對象所在列表中的倒序出現的第一個索引位置
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;
}

接下來再來看看ArrayList中刪除元素的過程:

 // 移除列表中指定索引位置的元素,同時被刪除元素的後續元素分別需要往前移動一位
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;
}

 // 先獲取該列表中指定元素的索引位置,然後在執行刪除操作
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;
}

 // 同remove()一樣,但區別在於不需要驗證索引是否有效
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 void clear() {
	modCount++;

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

	size = 0;
}

// 保留列表中與集合c中相同的元素
public boolean retainAll(Collection<?> c) {
	Objects.requireNonNull(c);
	return batchRemove(c, true);
}
// 批量移除操作,移除列表中與集合c中不相同的元素
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;
		}
		// 若 w==size 則表示數組將要清空
		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;
}

 

最後關於arraycopy()的說明和對批量刪除操作時finally模塊中方法進行的測試如下:

/***
* @param      src      the source array. 源數組 
* @param      srcPos   starting position in the source array. 源數組要複製的起始位置
* @param      dest     the destination array. 目標數組
* @param      destPos  starting position in the destination data. 目標數組複製的起始位置 
* @param      length   the number of array elements to be copied. 複製的長度
*/
public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);
@Test
public void arrayCopyTest(){
	Collection c =  Arrays.asList(new Integer[]{3,6,7,9});
	Integer[] arrayInteger = new Integer[]{1,2,3,4,5,6,7,8,9,10};  // 源數組

	int r = 0,w=0, size = arrayInteger.length;

	for (; r < size; r++)
		if (c.contains(arrayInteger[r]) == true)
			arrayInteger[w++] = arrayInteger[r];

	System.out.println("size=="+size +", r=="+r+", w=="+w + ", length=="+(size-r));
	for (Integer e : arrayInteger){
		System.out.print(e+",");
	}
	System.out.println("\n");
	r = 9;
	if (r != size) {
		System.arraycopy(arrayInteger, r, arrayInteger, w, size - r);
		w += size - r;
	}
	for (Integer e : arrayInteger){
		System.out.print(e+",");
	}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章