Jdk8
總結
- 擴容和刪除
ArrayList 就是一個實現了List介面的可自動擴容的陣列,當新增元素的時候它會嘗試擴容,擴容的標準是變為原來的1.5倍**,當刪除元素的時候,它會左移元素,避免陣列出現"空位"
- 容量
new ArrayList<>() 初始化容量為0,等到第一次add的時候再初始化為10
- 有序集合
可以儲存重複值和null值
示例:
public static void main(String[] args) {
List<String> a=new ArrayList<>();
a.add(null);
a.add(null);
a.add(null);
System.out.println(a.size());
}
輸出:
3
- ArrayList 是快速失敗的,在遍歷的同時當集合被修改後會丟擲ConcurrentModificationException,可以使用Iterator 的刪除方法來避免這個問題
- 非執行緒安全的,如果你想在多執行緒環境中使用,可以使用Vector 或者它的執行緒安全包裝類
- 擴充套件
作業系統的區域性性原理,陣列的連續儲存空間的特性充分使用了區域性性原理,也就是說硬體的快取記憶體加速了陣列的訪問
效能
- Adding an element- 如果你使用的是 add(E e) 方法新增一個元素到ArrayList末尾 ,它的時間複雜度 O(1);但是當空間不足引發擴容的時候,會導致新建陣列然後複製資料,這個時候它的時間複雜度 O(n) ;當你使用 add(int index, E element)的時候它的演算法複雜度是 O(n - index) 也就是 O(n)
- Retrieving an element- 當你使用get(int index) 的時候,它的時間複雜度是 O(1),因為陣列可以直接根據下標進行定位
- Removing an element- 當你使用 remove(int index) 它的時間複雜度是 O(n - index) ,因為它涉及到移動元素
- Traverse - 遍歷的時間時間複雜度是O(n),也就是依賴於Capacity 的大小,如果你比較重視遍歷的效能,就請不要不要給它設定一個很大的初始容量
UML
底層是一個Object[],新增到ArrayList中的資料儲存在了elementData屬性中。
- 當呼叫new ArrayList<>()時,將一個空陣列 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 賦值給了elementData,這個時候集合的長度size為預設長度0
- 例如當呼叫new ArrayList<>(100)時,根據傳入的長度,new一個Object[100]賦值給elementData,當然如果玩兒的話,傳了一個0,那麼將一個空陣列 EMPTY_ELEMENTDATA 賦值給了elementData
- 例如當呼叫new ArrayList<>(new HashSet())時,根據原始碼,我們可知,可以傳遞任何實現了Collection介面的類,將傳遞的集合呼叫toArray()方法轉為陣列內賦值給elementData
構造方法
無參構造
建立一個空的使用預設容量的list(預設是0,第一次add會初始化為10)
//預設建立一個ArrayList集合
List<String> list = new ArrayList<>();
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
指定初始容量
建立一個空的指定容量的list
//建立一個初始化長度為100的ArrayList集合
List<String> initlist = new ArrayList<>(100);
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);
}
}
其他集合作為引數
//將其他型別的集合轉為ArrayList
List<String> setList = new ArrayList<>(new HashSet());
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
構造一個包含指定集合元素的列表,其順序由集合的迭代器返回。當傳入的集合引數為空的話,丟擲NullPointerException,因為它會呼叫該集合的toArray 方法,和HashTable 裡面呼叫key 的hashcode 方法的原理一樣
當集合是一個空的集合的話,elementData = EMPTY_ELEMENTDATA和指定0是initialCapacity的效果一樣
注意在傳入集合的ArrayList的構造方法中,有這樣一個判斷
if (elementData.getClass() != Object[].class),
給出的註釋是:c.toArray might (incorrectly) not return Object[] (see 6260652),即呼叫toArray方法返回的不一定是Object[]型別,檢視Collection介面的定義
Object[] toArray();
我們發現返回的確實是Object[],那麼為什麼還會有這樣的判斷呢?
如果有一個類CustomList繼承了ArrayList,然後重寫了toArray()方法呢。。
public class CustomList<E> extends ArrayList {
@Override
public Integer [] toArray() {
return new Integer[]{1,2};
};
public static void main(String[] args) {
Object[] elementData = new CustomList<Integer>().toArray();
System.out.println(elementData.getClass());
System.out.println(Object[].class);
System.out.println(elementData.getClass() == Object[].class);
}
}
執行結果:
class [Ljava.lang.Integer;
class [Ljava.lang.Object;
false
接著說,如果傳入的集合型別和我們定義用來儲存新增到集合中值的Object[]型別不一致時,ArrayList做了什麼處理?讀原始碼看到,呼叫了
Arrays.copyOf(elementData, size, Object[].class);
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
我們發現定義了一個新的陣列,將原陣列的資料複製到了新的陣列中去。
思考
我們在檢視 ArrayList 的實現類原始碼時,你會發現物件陣列 elementData 使用了 transient 修飾,我們知道 transient 關鍵字修飾該屬性,則表示該屬性不會被序列化,然而我們並沒有看到文件中說明 ArrayList 不能被序列化,這是為什麼?<br />
ArrayList 屬性主要由陣列長度 size、物件陣列 elementData、初始化容量 default_capacity 等組成, 其中初始化容量預設大小為 10
// 預設初始化容量
private static final int DEFAULT_CAPACITY = 10;
// 物件陣列
transient Object[] elementData;
// 陣列長度
private int size;
從 ArrayList 屬性來看,它沒有被任何的多執行緒關鍵字修飾,但 elementData 被關鍵字 transient 修飾了。這就是我在上面提到的第一道測試題:transient 關鍵字修飾該欄位則表示該屬性不會被序列化,但 ArrayList 其實是實現了序列化介面,這到底是怎麼回事呢?
這還得從"ArrayList是基於陣列實現"開始說起,由於 ArrayList 的陣列是基於動態擴增的,所以並不是所有被分配的記憶體空間都儲存了資料。
如果採用外部序列化法實現陣列的序列化,會序列化整個陣列。ArrayList 為了避免這些沒有儲存資料的記憶體空間被序列化,內部提供了兩個私有方法 writeObject 以及 readObject 來自我完成序列化與反序列化,從而在序列化與反序列化陣列時節省了空間和時間。因此使用 transient 修飾陣列,是防止物件陣列被其他外部方法序列化。
看到這裡就點個贊吧?分享更多技術文章去幫助更多的人,這裡有我所有知識庫喲~ ?