1. 程式人生 > >java集合【12】——— ArrayList,LinkedList,Vector的相同點與區別是什麼?

java集合【12】——— ArrayList,LinkedList,Vector的相同點與區別是什麼?

[TOC] 要想回答這個問題,可以先把各種都講特性,然後再從底層儲存結構,執行緒安全,預設大小,擴容機制,迭代器,增刪改查效率這幾個方向入手。 ## 特性列舉 ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210306021250.png) - `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`在底層是雙向連結串列結構。 ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210305104801.png) ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210305104608.png) ![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210305104903.png) ## 執行緒安全性不同 ArrayList和LinkedList都不是執行緒安全的,但是Vector是執行緒安全的,其底層是用了大量的synchronized關鍵字,效率不是很高。 如果需要ArrayList和LinkedList是執行緒安全的,可以使用Collections類中的靜態方法synchronizedList(),獲取執行緒安全的容器。 ## 預設的大小不同 ArrayList如果我們建立的時候不指定大小,那麼就會初始化一個預設大小為10,`DEFAULT_CAPACITY`就是預設大小。 ```java private static final int DEFAULT_CAPACITY = 10; ``` Vector也一樣,如果我們初始化,不傳遞容量大小,什麼都不指定,預設給的容量是10: ``` java 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`。 測試三種結構在頭部不斷新增元素的結果: ```java 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費時最多,基本驗證了結果: ```txt ArrayList:7715 LinkedList:111 Vector:8106 ``` 測試get的時間效能,往每一個裡面初始化10w個數據,然後每次get出來: ```java 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`在單執行緒環境其實差不多,多執行緒環境會比較明顯,這裡就不測試了: ``` txt ArrayList : 18 LinkedList : 61480 Vector : 21 ``` 測試刪除操作的程式碼如下,刪除的時候我們是不斷刪除第0個元素: ```java 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`效率還要高。因為是單執行緒的環境,沒有觸發競爭的關係。 ```txt ArrayList: 7177 LinkedList: 34 Vector: 6713 ``` 下面來測試一下,vector多執行緒的環境,首先兩個執行緒,每個刪除5w元素: ```java 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等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。 [2020年我寫了什麼?](http://aphysia.cn/archives/2020) [開源刷題筆記](https://damaer.github.io/CodeSolution/#/) 平日時間寶貴,只能使用晚上以及週末時間學習寫作,關注我,我們一起成