java集合【12】——— ArrayList,LinkedList,Vector的相同點與區別是什麼?
阿新 • • 發佈:2021-03-26
[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/#/)
平日時間寶貴,只能使用晚上以及週末時間學習寫作,關注我,我們一起成