前言:
一.在其他播客上看到下面這段話,可以說是總結的非常精闢了。讀者們可以仔細品味:
ArrayList和LinkedList在性能上各 有優缺點,都有各自所適用的地方,總的說來可以描述如下:
1.對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對 ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。
2.在ArrayList的 中間插入或刪除一個元素意味着這個列表中剩餘的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。
3.LinkedList不 支持高效的隨機元素訪問。
4.ArrayList的空 間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間
可以這樣說:當操作是在一列 數據的後面添加數據而不是在前面或中間,並且需要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操作是在一列數據的前面或中 間添加或刪除數據,並且按照順序訪問其中的元素時,就應該使用LinkedList了。
所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那麼使用Vector或ArrayList都可以。如果是對其它指定位置的插入、刪除操作,最好選擇LinkedList
二:下面我從源碼上分析ArrayList:
屬性:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 序列化 ID
private static final long serialVersionUID = 8683452581122892189L;
/**
* ArrayList 默認的數組容量
*/
private static final int DEFAULT_CAPACITY = 10;
// 一個默認的空數組
private static final Object[] EMPTY_ELEMENTDATA = {};
// 在調用無參構造方法的時候使用該數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存儲 ArrayList 元素的數組
// transient 關鍵字這裏簡單說一句,被它修飾的成員變量無法被 Serializable 序列化
transient Object[] elementData; // non-private to simplify nested class access
// ArrayList 的大小,也就是 elementData 包含的元素個數
private int size;
}
實現了 Serializable 是序列化接口,因此它支持序列化,能夠通過序列化傳輸。
實現了 Cloneable 接口,能被克隆。
實現了Iterable 接口,可以被迭代器遍歷
實現了 Collection ,擁有集合操作的方法
實現了 List 接口,擁有增刪改查等方法
實現了 RandomAccess 隨機訪問接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問。
構造方法:
可以看到elementData 數組 就是ArrayList用來存儲數據的數組。
// 指定大小的構造方法,如果傳入的是 0 ,直接使用 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);
}
}
// 調用該構造方法構造一個默認大小爲 10 的數組,但是此時大小未指定,
// 還是空的,在第一次 add 的時候指定
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 傳入一個集合類
// 首先直接利用Collection.toArray()方法得到一個對象數組,並賦值給elementData
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 出錯的時候,使用Arrays.copyOf 生成一個新數組賦值給 elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果集合c元素數量爲0,則將空數組EMPTY_ELEMENTDATA賦值給elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
方法:
我們重點說明 addAll 方法,涉及到ArrayList的數組擴容原理。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
1. add方法調用ensureCapacityInternal()方法,可以看到如果初始大小爲{},則數組默認大小爲10,繼續調用
ensureExplicitCapacity()方法。
private void ensureCapacityInternal(int minCapacity) {
// 如果創建 ArrayList 時候,使用的無參的構造方法,那麼就取默認容量 10 和最小需要的容量(當前 size + 1 )中大的一個確定需要的容量。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
2. 主要代碼是 :
int newCapacity = oldCapacity + (oldCapacity >> 1);
這裏表示新的數組大小爲 原數組大小 + 原數組大小向右位移 1 位。後面我專門做右位移運算講解。
private void ensureExplicitCapacity(int minCapacity) {
// 修改 +1
modCount++;
// 如果 minCapacity 比當前容量大, 就執行grow 擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 拿到當前的容量
int oldCapacity = elementData.length;
// oldCapacity >> 1 意思就是 oldCapacity/2,所以新容量就是增加 1/2.
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量小於,需要最小擴容的容量,以需要最小容量爲準擴容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大於允許的最大容量,則以 Inerger 的最大值進行擴容
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用 Arrays.copyOf 函數進行擴容。
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 允許的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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的數組擴容原理介紹:
到這裏我們可以看到 當我們創建一個ArrayList集合時。elementData 數組初始大小爲0,當我們添加一個元素時elementData 大小爲默認大小10,集合大小超過默認大小時,elementData 爲之前的1.5倍大小,實際上是原數組大小 + 原數組大小右移1位。
下面我們來做一次計算:當我不停的往ArrayList 添加數據時:
List<String> list = new ArrayList<String>();
System.out.println(list);
for(int i = 0; i<100; i++) {
list.add("a");
}
System.out.println(list);
List<String> list = new ArrayList<String>(); //elementData 大小爲0
第一次添加時 //elementData 大小爲10
list.size()>10時 // 10的二進制大小 爲 1010 右移一位大小爲 0101,二級制 0101 的大小爲5,所以elementData 大小爲15
list.size()>15時 // 10的二進制大小 爲 1111 右移一位大小爲 0111,二級制 0111 的大小爲7,所以elementData 大小爲22
所以 當我 創建一個ArrayList時
當我add一個數據時 elementData大小爲10
當集合大小超過10的時候,elementData大小爲15
當集合大小超過15時,elementData大小爲22
至此,驗證成功。