其實 Java 集合框架也叫做容器,主要由兩大介面派生而來,一個是 collection
,主要存放物件的集合。另外一個是Map
, 儲存著鍵值對(兩個物件)的對映表。
下面就來說說 List
介面,List
儲存的元素是有序、可重複的。其下有三個子介面,ArrayList、LinkedList 和 vector。
一、 ArrayList概述
ArrayList
底層資料結構是基於 Object 陣列來實現的,我們看看它的底層介面原始碼:
1. ArrayList 實現的介面
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
其中繼承的介面中的 RandomAccess
、Cloneable
和 Serializable
只是標記介面,他們的介面內部沒有具體的方法和引數:
public interface RandomAccess {}
public interface Cloneable {} //覆蓋clone(),能被克隆
public interface Serializable {} //支援序列化,能通過序列化傳輸
標記介面是電腦科學中的一種設計思路。程式語言本身不支援為類維護後設資料。而標記介面則彌補了這個功能上的缺失——一個類實現某個沒有任何方法的標記介面,實際上標記介面從某種意義上說就成為了這個類的後設資料之一。執行時,通過程式語言的反射機制,我們就可以在程式碼裡拿到這種後設資料。
以Serializable介面為例。一個類實現了這個介面,說明它可以被序列化。因此,我們實際上通過Serializable這個介面,給該類標記了“可被序列化”的後設資料,打上了“可被序列化”的標籤。這也是標記/標籤介面名字的由來。
此外AbstractList
繼承AbstractCollection
抽象類,實現List
介面。它實現了 List 的一些基本操作如(get,set,add,remove),是第一實現隨機訪問方法的集合類,但是不支援新增和替換。
2.ArrayList 的成員屬性
private static final int DEFAULT_CAPACITY = 10; //預設初始容量為10
private static final Object[] EMPTY_ELEMENTDATA = {}; //空陣列,用於空例項
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//用於預設大小空例項的共享空陣列例項。
transient Object[] elementData; //儲存ArrayList資料的陣列,transient修飾表示陣列預設不會被序列化
private int size; //ArrayList中陣列的個數
二、ArrayList 中的方法
Java ArrayList 中常用的方法:
方法 | 描述 |
---|---|
add() | 將元素插入到指定位置的 arraylist 中 |
addAll() | 新增集合中的所有元素到 arraylist 中 |
clear() | 刪除 arraylist 中的所有元素 |
clone() | 複製一份 arraylist |
contains() | 判斷元素是否在 arraylist |
get() | 通過索引值獲取 arraylist 中的元素 |
indexOf() | 返回 arraylist 中元素的索引值 |
removeAll() | 刪除存在於指定集合中的 arraylist 裡的所有元素 |
remove() | 刪除 arraylist 裡的單個元素 |
size() | 返回 arraylist 裡元素數量 |
isEmpty() | 判斷 arraylist 是否為空 |
subList() | 擷取部分 arraylist 的元素 |
set() | 替換 arraylist 中指定索引的元素 |
sort() | 對 arraylist 元素進行排序 |
toArray() | 將 arraylist 轉換為陣列 |
toString() | 將 arraylist 轉換為字串 |
ensureCapacity() | 設定指定容量大小的 arraylist |
lastIndexOf() | 返回指定元素在 arraylist 中最後一次出現的位置 |
retainAll() | 保留 arraylist 中在指定集合中也存在的那些元素 |
containsAll() | 檢視 arraylist 是否包含指定集合中的所有元素 |
trimToSize() | 將 arraylist 中的容量調整為陣列中的元素個數 |
removeRange() | 刪除 arraylist 中指定索引之間存在的元素 |
replaceAll() | 將給定的操作內容替換掉陣列中每一個元素 |
removeIf() | 刪除所有滿足特定條件的 arraylist 元素 |
forEach() | 遍歷 arraylist 中每一個元素並執行特定操作 |
具體的方法細節可以看這裡
三、ArrayList 中的擴容機制
在初始化時,ArrayList 有三種方式來進行初始化,以無參構造方法建立 ArrayList 時,實際上賦值的是一個空陣列。當真正對陣列進行新增元素時,才真正的給 ArrayList 分配容量,即陣列容量擴為10。
/**
*預設建構函式,使用初始容量10構造一個空列表(無引數構造)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 帶初始容量引數的建構函式。(使用者自己指定容量)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大於0
//建立initialCapacity大小的陣列
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等於0
//建立空陣列
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小於0,丟擲異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回
*如果指定的集合為null,throws NullPointerException。
*/
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;
}
}
/**
* 將指定的元素追加到此陣列的末尾。
*/
public boolean add(E e) {
//在增加元素前,先呼叫ensureCapacityInternal方法
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//比較當前元素個數和預設元素個數,如果小於10,則將最小容量設為預設值10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//繼續呼叫 ensureExplicitCapacity()方法
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//如果大於當前陣列預設長度,則進行擴容,呼叫grow()方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 要分配的最大陣列大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//在以前的容量基礎上增加舊容量的1/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//檢查比較新容量與最小容量的大小,取大值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//更新完新容量後,比較是否大於最大陣列大小 Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
//若大於最大陣列大小,則呼叫hugeCapacity()方法
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//對minCapacity和MAX_ARRAY_SIZE進行比較
//若minCapacity大,將Integer.MAX_VALUE作為新陣列的大小
//若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新陣列的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
四、ArrayList 相關面試題
1. System.arraycopy() 和 Arrays.copyOf() 的區別
聯絡:
看兩者原始碼可以發現 copyOf()
內部實際呼叫了 System.arraycopy()
方法
區別:
arraycopy()
需要目標陣列,將原陣列拷貝到你自己定義的陣列裡或者原陣列,而且可以選擇拷貝的起點和長度以及放入新陣列中的位置 copyOf()
是系統自動在內部新建一個陣列,並返回該陣列。
2. ArrayList 和 LinkedList 的區別
- 是否保證執行緒安全:
ArrayList
和LinkedList
都是不同步的,也就是不保證執行緒安全; - 底層資料結構:
Arraylist
底層使用的是Object
陣列;LinkedList
底層使用的是 雙向連結串列 資料結構(JDK1.6 之前為迴圈連結串列,JDK1.7 取消了迴圈。注意雙向連結串列和雙向迴圈連結串列的區別,下面有介紹到!) - 插入和刪除是否受元素位置的影響: ①
ArrayList
採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e)
方法的時候,ArrayList
會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是 O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element)
)時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ②LinkedList
採用連結串列儲存,所以對於add(E e)
方法的插入,刪除元素時間複雜度不受元素位置的影響,近似 O(1),如果是要在指定位置i
插入和刪除元素的話((add(int index, E element)
) 時間複雜度近似為o(n))
因為需要先移動到指定位置再插入。 - 是否支援快速隨機訪問:
LinkedList
不支援高效的隨機元素訪問,而ArrayList
支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index)
方法)。 - 記憶體空間佔用:
ArrayList
的空 間浪費主要體現在在 list 列表的結尾會預留一定的容量空間,而LinkedList
的空間花費則體現在它的每一個元素都需要消耗比ArrayList
更多的空間(因為要存放直接後繼和直接前驅以及資料)。
3. ArrayList 和 Vector 的區別
ArrayList
是List
的主要實現類,底層使用Object[ ]
儲存,適用於頻繁的查詢工作,執行緒不安全 ;Vector
是List
的古老實現類,底層使用Object[ ]
儲存,執行緒安全的。如下程式碼帶有synchronized
同步鎖,能夠保證執行緒安全。
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}