要想回答這個問題,可以先把各種都講特性,然後再從底層儲存結構,執行緒安全,預設大小,擴容機制,迭代器,增刪改查效率這幾個方向入手。
特性列舉
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等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。
平日時間寶貴,只能使用晚上以及週末時間學習寫作,關注我,我們一起成長吧~