一、ArrayList的概述
ArrayList 是我們開發中常用的一種數據結構,它的底層是基於數組實現的,是一個動態數組,容量你可以動態 增加,ArrayList實現Serializable 接口,他能支持序列化傳輸;實現了RandomAccess接口,支持快速隨機訪問,也就是通過下標可以實現快速訪問;實現了Cloneable 接口,意味着可以被克隆。
二、ArrayList 的源碼解析
首先看下 構造函數
/**
* Constructs an empty list with an initial capacity of ten.
*/
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;
}
}
①、第一個無參構造器 是將一個空數組賦值給 elementData;
②、而第二個有參數構造器是先判斷initialCapacity與0之間的關係,如果大於0,則創建一個大小爲initialCapacity的對象數組賦給elementData;如果等於0,則將EMPTY_ELEMENTDATA賦給 elementData, 如果小於0;則拋出異常;
③、第三個構造器將參數集合轉換成數組判斷集合是否爲空,爲空將 EMPTY_ELEMENTDATA 賦值給elementData,否則將轉換後的數組拷貝到elementData中,由此可知elementData就是ArrayList底層維護的數據
通過上面發現一個神奇的問題:在無參數構造函數中將 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 這個數組賦值給elementData但是在有參構造函數中initialCapacity大小爲0時 將 EMPTY_ELEMENTDATA 賦值給elementData, 帶着疑問繼續看源碼發現:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(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;
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);
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是在calculateCapacity 這個方法裏面使用的,這個方法是判斷默認容量大小的。
如果是無參的構造,他的默認容量會被設置爲10;
而如果是有參的話就會取minCapacity的值,這也體現出jdk源碼設計的微妙,如果我們在有參構造中將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA 那麼就會導致小於10的容量變成10,假如我們只存儲3個元素,那麼無形中就有7個空間被白白的浪費掉了。
接着往下走我們會發現,添加的時候會先去判斷容量是否夠用,如果夠用就不在去擴容,如果不夠用,就進行擴容,擴大到上次的1.5 倍,因爲數組的大小一旦聲明是無法修改的,源碼中是使用了copyOf 函數, copyOf 底層的原理是在內存中重新開闢一塊空間將已有的數據拷貝過去,這樣就在不影響原有數據的基礎上進行了擴容,可以編寫一個demo驗證一下:
public static void main(String[] args) {
// List<String> list = new ArrayList<>();
// System.out.println(list);
Object[] object = {1,2,3};
System.out.println(object);
object = Arrays.copyOf(object, 10);
System.out.println(object);
}
運行後發現使用 copyof 函數後並沒有指向當前數組,而是指向了其他,這也就證明了他不會在當前數組進行擴容,而是在其他的地方重新創建了一塊空間。
假設數據量很大的時候,會導致效率大大的降低,所以以後在使用ArrayList 的時候即使不知道具體大小,也可以指定一個大概的容量,這也就可以避免重複的擴容,降低效率.
通過剛纔的分析,還發現了一個問題是add 方法居然沒有加鎖,這樣如果在多線程的情況下,ArrayList 的add方法是線程不安全的,容易產生數組越界問題.
1.當線程A調用ensureCapacityInternal 方法時 這時size=9 判斷容量後發現不用擴容
2.當線程B 進入時 他得到size 也是9,而elementData的大小是10,判斷容量後發現不用擴容
3.當線程A去執行elementData[size++] = e 操作時,size++, size 的大小變成了10,
4.當線程B再去執行elementData[size++] = e 操作時,就會產生數組越界的問題,
解決方法:爲了解決線程安全問題可以使用 Collections.synchronizedList(),但是會降低效率 如:
List list = Collections.synchroizedList(new ArrayList<>());
刪除 remove():
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;
}
刪除的方法比較簡單,每次進行數組的複製,然後將舊的 elementData=null 讓垃圾回收機制去回收。
Arrylist 的 get ,set,clear 也沒有其他複雜的操作, 都是一些對數組的簡單操作。
ArrayList的優點
1.ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,因此查找也就是get的時候非常快。
2.可以做到自動擴容,無需開發者關心數組大小
ArrayList的缺點
1.因爲每次插入和刪除都需要對數組進行拷貝操作,所以插入和刪除的效率並不高
2.是線程不安全