要想回答這個問題,可以先把各種都講特性,然后再從底層存儲結構,線程安全,默認大小,擴容機制,迭代器,增刪改查效率這幾個方向入手。
特性列舉

ArrayList:動態數組,使用的時候,只需要操作即可,內部已經實現擴容機制。- 線程不安全
- 有順序,會按照添加進去的順序排好
- 基於數組實現,隨機訪問速度快,插入和刪除較慢一點
- 可以插入
null元素,且可以重復
Vector和前面說的ArrayList很是類似,這里說的也是1.8版本,它是一個隊列,但是本質上底層也是數組實現的。同樣繼承AbstractList,實現了List,RandomAcess,Cloneable,java.io.Serializable接口。具有以下特點:- 提供隨機訪問的功能:實現
RandomAcess接口,這個接口主要是為List提供快速訪問的功能,也就是通過元素的索引,可以快速訪問到。 - 可克隆:實現了
Cloneable接口 - 是一個支持新增,刪除,修改,查詢,遍歷等功能。
- 可序列化和反序列化
- 容量不夠,可以觸發自動擴容
- **最大的特點是:線程安全的*,相當於線程安全的
ArrayList。
- 提供隨機訪問的功能:實現
- LinkedList:鏈表結構,繼承了
AbstractSequentialList,實現了List,Queue,Cloneable,Serializable,既可以當成列表使用,也可以當成隊列,堆棧使用。主要特點有:- 線程不安全,不同步,如果需要同步需要使用
List list = Collections.synchronizedList(new LinkedList()); - 實現
List接口,可以對它進行隊列操作 - 實現
Queue接口,可以當成堆棧或者雙向隊列使用 - 實現Cloneable接口,可以被克隆,淺拷貝
- 實現
Serializable,可以被序列化和反序列化
- 線程不安全,不同步,如果需要同步需要使用
底層存儲結構不同
ArrayList和Vector底層都是數組結構,而LinkedList在底層是雙向鏈表結構。



線程安全性不同
ArrayList和LinkedList都不是線程安全的,但是Vector是線程安全的,其底層是用了大量的synchronized關鍵字,效率不是很高。
如果需要ArrayList和LinkedList是線程安全的,可以使用Collections類中的靜態方法synchronizedList(),獲取線程安全的容器。
默認的大小不同
ArrayList如果我們創建的時候不指定大小,那么就會初始化一個默認大小為10,DEFAULT_CAPACITY就是默認大小。
private static final int DEFAULT_CAPACITY = 10;
Vector也一樣,如果我們初始化,不傳遞容量大小,什么都不指定,默認給的容量是10:
public Vector() {
this(10);
}
而LinkedList底層是鏈表結構,是不連續的存儲空間,沒有默認的大小的說法。
擴容機制
ArrayList和Vector底層都是使用數組Object[]來存儲,當向集合中添加元素的時候,容量不夠了,會觸發擴容機制,ArrayList擴容后的容量是按照1.5倍擴容,而Vector默認是擴容2倍。兩種擴容都是申請新的數組空間,然后調用數組復制的native函數,將數組復制過去。
Vector可以設置每次擴容的增加容量,但是ArrayList不可以。Vector有一個參數capacityIncrement,如果capacityIncrement大於0,那么擴容后的容量,是以前的容量加上擴展系數,如果擴展系數小於等於0,那么,就是以前的容量的兩倍。
迭代器
LinkedList源碼中一共定義了三個迭代器:
Itr:實現了Iterator接口,是AbstractList.Itr的優化版本。ListItr:繼承了Itr,實現了ListIterator,是AbstractList.ListItr優化版本。ArrayListSpliterator:繼承於Spliterator,Java 8 新增的迭代器,基於索引,二分的,懶加載器。
Vector和ArrayList基本差不多,都是定義了三個迭代器:
Itr:實現接口Iterator,有簡單的功能:判斷是否有下一個元素,獲取下一個元素,刪除,遍歷剩下的元素ListItr:繼承Itr,實現ListIterator,在Itr的基礎上有了更加豐富的功能。VectorSpliterator:可以分割的迭代器,主要是為了分割以適應並行處理。和ArrayList里面的ArrayListSpliterator類似。
LinkedList里面定義了三種迭代器,都是以內部類的方式實現,分別是:
ListItr:列表的經典迭代器DescendingIterator:倒序迭代器LLSpliterator:可分割迭代器
增刪改查的效率
理論上,ArrayList和Vector檢索元素,由於是數組,時間復雜度是O(1),在集合的尾部插入或者刪除是O(1),但是其他的地方增加,刪除,都是O(n),因為涉及到了數組元素的移動。但是LinkedList不一樣,LinkedList不管在任何位置,插入,刪除都是O(1)的時間復雜度,但是LinkedList在查找的時候,是O(n)的復雜度,即使底層做了優化,可以從頭部/尾部開始索引(根據下標在前一半還是后面一半)。
如果插入刪除比較多,那么建議使用LinkedList,但是它並不是線程安全的,如果查找比較多,那么建議使用ArrayList,如果需要線程安全,先考慮使用Collections的api獲取線程安全的容器,再考慮使用Vector。
測試三種結構在頭部不斷添加元素的結果:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
addArrayList();
addLinkedList();
addVector();
}
public static void addArrayList(){
List list = new ArrayList();
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void addLinkedList(){
List list = new LinkedList();
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void addVector(){
List list = new Vector();
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
}
測出來的結果,LinkedList最小,Vector費時最多,基本驗證了結果:
ArrayList:7715
LinkedList:111
Vector:8106
測試get的時間性能,往每一個里面初始化10w個數據,然后每次get出來:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
getArrayList();
getLinkedList();
getVector();
}
public static void getArrayList(){
List list = new ArrayList();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.get(i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void getLinkedList(){
List list = new LinkedList();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.get(i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void getVector(){
List list = new Vector();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.get(i);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
}
測出來的時間如下,LinkedList 執行get操作確實耗時巨大,Vector和ArrayList在單線程環境其實差不多,多線程環境會比較明顯,這里就不測試了:
ArrayList : 18
LinkedList : 61480
Vector : 21
測試刪除操作的代碼如下,刪除的時候我們是不斷刪除第0個元素:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
removeArrayList();
removeLinkedList();
removeVector();
}
public static void removeArrayList(){
List list = new ArrayList();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.remove(0);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void removeLinkedList(){
List list = new LinkedList();
for(int i=0;i<100000;i++){
list.add(0,i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.remove(0);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
public static void removeVector(){
List list = new Vector();
for(int i=0;i<100000;i++){
list.add(i);
}
long startTime = System.nanoTime();
for(int i=0;i<100000;i++){
list.remove(0);
}
long endTime = System.nanoTime();
System.out.println((endTime-startTime)/1000/60);
}
}
測試結果,LinkedList確實效率最高,但是Vector比ArrayList效率還要高。因為是單線程的環境,沒有觸發競爭的關系。
ArrayList: 7177
LinkedList: 34
Vector: 6713
下面來測試一下,vector多線程的環境,首先兩個線程,每個刪除5w元素:
package com.aphysia.offer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
removeVector();
}
public static void removeVector() {
List list = new Vector();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
list.remove(0);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
list.remove(0);
}
}
});
long startTime = System.nanoTime();
thread1.start();
thread2.start();
while (!list.isEmpty()) {
}
long endTime = System.nanoTime();
System.out.println((endTime - startTime) / 1000 / 60);
}
}
測試時間為:12668
如果只使用一個線程,測試的時間是:8216,這也從結果說明了確實Vector在多線程的環境下,會競爭鎖,導致執行時間變長。
總結一下
- ArrayList
- 底層是數組,擴容就是申請新的數組空間,復制
- 線程不安全
- 默認初始化容量是10,擴容是變成之前的1.5倍
- 查詢比較快
- LinkedList
- 底層是雙向鏈表,可以往前或者往后遍歷
- 沒有擴容的說法,可以當成雙向隊列使用
- 增刪比較快
- 查找做了優化,index如果在前面一半,從前面開始遍歷,index在后面一半,從后往前遍歷。
- Vector
- 底層是數組,幾乎所有方法都加了Synchronize
- 線程安全
- 有個擴容增長系數,如果不設置,默認是增加原來長度的一倍,設置則增長的大小為增長系數的大小。
【刷題筆記】
Github倉庫地址:https://github.com/Damaer/codeSolution
筆記地址:https://damaer.github.io/codeSolution/
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分布式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花里胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
平日時間寶貴,只能使用晚上以及周末時間學習寫作,關注我,我們一起成長吧~
