其實 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);
}
}
4. ArrayList 和 CopyOnWriteArrayList 的區別
CopyOnWriteArrayList 和 Vector 一樣也是線程安全的 List 。它在 java.util.concurrent 的包下。它的線程安全主要是通過讀寫分離來實現的。寫操作在一個復制的數組上實現(加鎖),讀操作還是在原數組中進行。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}
特點:適合讀多寫少的應用場景
缺點:
- 內存占用:在寫操作時需要復制一個新的數組,使得內存占用為原來的兩倍左右;
- 數據不一致:讀操作不能讀取實時性的數據,因為部分寫操作的數據還未同步到讀數組中;