thinking in java (十一) ----- 集合之ArrayList
ArrayList概述
ArrayList是實現了List介面的動態陣列,所謂的動態是其大小可變。允許包括null在內的元素。除了實現List介面外,此類還提供了一些方法來操控內部來儲存列表的陣列大小。
每個ArrayList例項都有一個容量,該容量是指用來儲存元素的陣列大小。預設初始容量是10,隨著ArrayList元素的增加,它的容量也會一直增加。在每一次增加新的元素的時候,ArrayList都會檢查是否需要擴容,擴容操作帶來資料向新的陣列的COPY,所以我們如果知道具體業務數量(一般不能),在構造ArrayList的時候可以指定一個初始容量,這樣會減少擴容操作。
需要注意的是,ArrayList不是同步的,如果多執行緒訪問一個ArrayList例項,而其中至少一個執行緒從結構上改變了元素列表,那麼必須保持外部同步,為了同步,最好是在建立時完成同步操作:
List list = Collection。synchronizedList(new ArrayList(...));
ArrayList原始碼分析
- 底層使用陣列實現
private static final int DEFAULT_CAPACITY = 10;
————————————————————
預設初始容量10
transient Object[] elementData;
+++++++++++++++++
沒必要持久化其值
- 建構函式
ArrayList有三個建構函式
ArrayList():預設建構函式,提供初始容量為10的空列表
ArrayList(int initialCapacity):構造一個含有指定初始容量的空列表
public ArrayList(Collection<? extends E> c) :構造一個包含指定collection的元素的列表,
原始碼:
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } —————————————————————————————————— 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); } } —————————————————————————— 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列表,一個有初始容量的空列表,一個指定collection元素的列表,
- 新增(add)
ArrayList提供了add(E e) ,add(index,E e),addAll(Collection(<? extends E> c),addAll(index,Collection(<? extends E> c)(這倆只能新增泛型)set(index,E e)這五種方法向ArrayList新增元素。
add(E e):
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
——————————————————
將指定的元素插入此列表中的尾巴
ensureCapacity方法是擴容操作,elementData[size++] = e;將列表末尾元素指向e
add(index,E e):
public void add(int index, E element) {
//判斷索引位置是否正確
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
//擴容檢測
ensureCapacity(size+1);
/*
* 對源陣列進行復制處理(位移),從index + 1到size-index。
* 主要目的就是空出index位置供資料插入,
* 即向右移動當前位於該位置的元素以及所有後續元素。
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在指定位置賦值
elementData[index] = element;
size++;
}
這個方法中最根本的方法是arraycopy方法 ,該方法的根本目的是將index位置空出來以供新資料的插入,這裡需要進行陣列的右移,這是很麻煩耗時的。多以如果指定的資料集合要進行大量插入的話,建議使用linkedList。
addAll(Collection(<? extends E> c):按照指定collection的迭代器返回的元素順序,將新增的元素放在列表的末尾
public boolean addAll(Collection<? extends E> c) {
// 將集合C轉換成陣列
Object[] a = c.toArray();
int numNew = a.length;
// 擴容處理,大小為size + numNew
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
這個方法無非就是使用System.arraycopy()方法將C集合(先準換為陣列)裡面的資料複製到elementData陣列中。
addAll(index,Collection(<? extends E> c):從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
//判斷位置是否正確
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
//轉換成陣列
Object[] a = c.toArray();
int numNew = a.length;
//ArrayList容器擴容處理
ensureCapacity(size + numNew); // Increments modCount
//ArrayList容器陣列向右移動的位置
int numMoved = size - index;
//如果移動位置大於0,則將ArrayList容器的資料向右移動numMoved個位置,確保增加的資料能夠增加
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//新增陣列
System.arraycopy(a, 0, elementData, index, numNew);
//容器容量變大
size += numNew;
return numNew != 0;
}
set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) {
//檢測插入的位置是否越界
RangeCheck(index);
E oldValue = (E) elementData[index];
//替代
elementData[index] = element;
return oldValue;
}
- 刪除
ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四個方法進行元素的刪除。
remove(int index):移除此列表中指定位置上的元素。
remove(Object o):移除此列表中首次出現的指定元素(如果存在)。
removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和toIndex(不包括)之間的所有元素。
- 查詢
ArrayList提供了get(int index)用讀取ArrayList中的元素。由於ArrayList是動態陣列,所以我們完全可以根據下標來獲取ArrayList中的元素,而且速度還比較快,故ArrayList長於隨機訪問。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
- 擴容
我們可以看到,在新增方法的原始碼都含有一個函式,ensureCapacity()方法,該方法就是擴容方法,如果新增元素後容量大過初始容量。就會進行擴容操作。所以當我們知道業務資料量或者需要插入大量元素的時候,我們使用ensureCapacity()方法來手動新增初始容量,以減少再擴容的操作
為什麼每次擴容處理會是1.5倍,而不是2.5、3、4倍呢?通過google查詢,發現1.5倍的擴容是最好的倍數。因為一次性擴容太大(例如2.5倍)可能會浪費更多的記憶體(1.5倍最多浪費33%,而2.5被最多會浪費60%,3.5倍則會浪費71%……)。但是一次性擴容太小,需要多次對陣列重新分配記憶體,對效能消耗比較嚴重。所以1.5倍剛剛好,既能滿足效能需求,也不會造成很大的記憶體消耗。