長久以來,ArrayList憑借著自生的存儲結構優點以及簡單好用的操作方法有著很高的曝光使用率。相信很多朋友都對ArrayList的優缺點倒背如流了,比如有序,訪問元素速度快,插入和刪除元素效率較慢等,本篇文章也會圍繞著幾點來分析說明,讓大家從本質上來理解這一集合類。
開局一張圖,剩下就全靠我編了。
step1:創建集合添加元素
大家都清楚ArrayList是基于數組來存儲元素的。數組是在內存中開辟一塊連續的空間,中間不留空,通過索引就能找到元素。如上圖所示,班級在操場上組織開展活動,至少得需要申請告訴學校自己班級在何時占用何地,以及人數規模。在ArrayList中創建一個大小為10的數組,供班級使用。
public class ArrayList<T> {
private Object[] mElementData;
private int mSize;
public ArrayList(int initSize){
mElementData = new Object[10];
}
public void add(T element){
mElementData[mSize] = element;
mSize++;
}
}
再讓女同學們集合站好。
public static void main(String[] args) {
ArrayList<GirlStudent> studentList = new ArrayList<>(10);
studentList.add(new GirlStudent(18,175));
studentList.add(new GirlStudent(18,174));
studentList.add(new GirlStudent(18,172));
studentList.add(new GirlStudent(18,171));
studentList.add(new GirlStudent(18,170));
studentList.add(new GirlStudent(18,169));
studentList.add(new GirlStudent(18,168));
studentList.add(new GirlStudent(18,167));
studentList.add(new GirlStudent(18,166));
}
step2:插入元素和刪除元素:
這時候又來了一位遲到的女同學,這位女同學個子也挺高的,有173。得站在第三個位置,所以先得麻煩后面7位同學都往后摞一摞,給遲到的女同學讓出位置來,遲到的女同學再入隊。有女同學出隊也如此,當有女同學身體不適需要休息,出隊后為了隊形整齊,空出的位置也需要后面的女同學依次向前走一步補齊。ArrayList中通過執行System.arraycopy()方法以復制數組的方式來完成刪除,插入,擴容等功能,說明如下:
/**
*
* @param src 原數組
* @param srcPos 從原數組哪個位置開始需要復制
* @param dest 目標數組
* @param destPos 從目標數組哪個位置開始復制
* @param length 原數組需要復制多少個到目標數組
*/
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
在ArrayList中如下:
public void add(E element,int index){
System.arraycopy(mElementData, index, mElementData, index+1, mSize-index);
mElementData[index] = element;
mSize++;
}
public void remove(int index){
System.arraycopy(mElementData, index+1, mElementData, index, mSize-index-1);
mElementData[--mSize] = null;
}
public static void main(String[] args) {
//入隊
studentList.add(new GirlStudent(18, 173), 2);
}
集合打印如下:
ArrayList擴容,插入刪除元素是犧牲時間和空間換來的。雖然System.arraycopy()方法很快。
step3:ArrayList擴容
剛集合完畢,老師突然安排讓隔壁班同學站在一起,這個時候就出現了一個問題,地方不夠用,裝不下這么多的人了。又得老師去申請一個大點的地方了:
GirlStudent[] newStudentArrays = new GirlStudent[30];
新地方找好了之后,在老師的帶領下,女同學們又去新的地方一個個站好了隊,再添加新的同學到隊列后邊:
System.arrayCopy(mElementData,0,newStudentArrays,0,newStudentArrays.length());
ArrayList每次在添加元素之前會先判斷是不是需要擴容了:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//大小為10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//ArrayList操作記錄
modCount++;
//是否需要擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
我們在創建ArrayList的時候,如果沒有給一個初始大小,在添加元素的時候會為我們創建一個大小為10的數組。如果添加元素后的大小和當前數組長度一樣,則需要擴容。即長度為10的數組,在添加第10個元素的時候就先進行擴容。比如上面遲到的女同學會先擴容再入隊。
step4:ArrayList擴容規則
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);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList根據當前數組長度的一半,1.5倍來擴容。
Arrays.copyof()方法追朔下去也是通過System.arrayCopy()做到擴容的目的。System.arrayCopy()是jni方法,底層用C/C++語言實現,直接對內存復制,避免在尋址上浪費時間效率很高>foreach>for循環。在開發中如果對數據量有一個大概的估計,最好在創建ArrayList時就給一個大小,避免數組擴容帶來不必要的資源浪費。
總結:
- ArrayList擴容,插入和刪除元素,會犧牲時間和空間,如果可以,盡量給一個初始大小。
- JDK1.7起,空參數構造創建ArrayList。只會是一個空數組。
- ArrayList訪問元素效率高。
- ArrayList按照順序添加元素,所以是有序的。
- ArrayList可以存Null值和重復值。
如果時間可以重來,我要studentList.add(Myself(),0);
不足之處,多多包涵