基準測試了 ArrayList 和 LinkedList ,發現我們一直用 ArrayList 也是沒什麼問題的
ArrayList 應該是 Java 中最常用的集合型別了,以至於我們說到集合就會自然而然的想到 ArrayList。很多同學都沒有用過除了 ArrayList 之外的其他集合,甚至於都已經忘了除了 ArrayList 之外的其他集合,例如 LinkedList、Vector 等。
那麼我們平時只用 ArrayList 是不是正確呢,我既然知道了 LinkedList、Vector ,是不是也要用一下它呢,那麼什麼情況下用呢。
Vector 是執行緒安全的集合型別,所以僅在多執行緒程式設計的時候才考慮是否用它,凡是為了多執行緒設計,必然在效能上有所損失, Vector 只是在每個方法上簡單的加上 synchronized
那麼現在就來比較一下 ArrayList 和 LinkedList,都說插入、刪除操作多的話用 LinkedList,插入操作多的話用 ArrayList,產生這種說法的大致依據如下。
ArrayList
內部是用 Object 陣列作為儲存結構,陣列是記憶體中連續的記憶體空間,並且繼承了 RandomAccess
介面,所以可以實現元素的快速訪問。但如果不是在末尾插入元素的話,需要拷貝插入位置之後的元素。
LinkedList
內部自己實現的 Node 連結串列,每個節點都指明瞭上一節點和下一節點, 所以不要求記憶體連續,並且插入資料只需要修改上一節點和下一節點的指標即可。但是訪問元素需要遍歷查詢,所以查詢元素較慢。
真的是這樣嗎?下面我做了一系列的測試,來看一下真實的情況。
插入資料
分別從頭部、尾部和隨機位置插入資料,初始的列表長度分別為 10、1w、10w 來測試幾種插入資料的效能,以平均耗時為依據。得到的結果如下:
頭部插入資料,ArrayList 耗時明顯大於 LinkedList ,得出結論頭部插入資料 LinkedList 效能優於 ArrayList,因為在頭部位置插入資料時,ArrayList 要拷貝插入位置之後的資料,往後移動一個位置。
尾部插入資料,ArrayList 耗時小於 LinkedList,得出結論:尾部插入資料,ArrayList 效能優於 LinkedList。
插入資料得出以下結論
插入位置 | 初始列表長度10 | 初始列表長度1w | 初始列表長度10w |
---|---|---|---|
頭部 | LinkedList 優於 ArrayList | LinkedList 優於 ArrayList | LinkedList 優於 ArrayList |
尾部 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
多次隨機位置 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
獲取資料
分別從頭部、尾部和隨機位置獲取資料,初始的列表長度分別為1000、10w、100w、1000w 來測試幾種獲取資料的效能,以吞吐量為判斷依據,吞吐量單位是 每毫秒內運算元。得到的結果如下:
頭部獲取資料,ArrayList 吞吐量大於 LinkedList,得出結論頭部獲取資料,ArrayList 效能優於 LinkedList
尾部獲取資料,ArrayList 吞吐量大於 LinkedList,得出結論尾部獲取資料,ArrayList 效能優於 LinkedList
多次隨機位置獲取資料,ArrayList 吞吐量大於 LinkedList,得出結論多次隨機位置獲取資料,ArrayList 效能優於 LinkedList
綜上,獲取資料操作的結論如下
獲取位置 | 列表長度1000 | 列表長度10w | 列表長度100w | 列表長度1000w |
---|---|---|---|---|
頭部 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
尾部 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
多次隨機位置 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
這是毋庸置疑的,實現了隨機訪問並且內部以陣列形式儲存資料的 ArrayList 擁有明顯的優勢。
刪除資料
分別從頭部、尾部和隨機位置獲取資料,初始的列表長度分別為1w、100w、1000w 來測試幾種刪除資料的效能,以吞吐量為判斷依據,吞吐量單位是 每毫秒內運算元。得到的結果如下:
頭部刪除資料,LinkedList 吞吐量大於 ArrayList,得出結論:頭部刪除資料,LinkedList 效能優於 ArrayList
尾部刪除資料,ArrayList 吞吐量大於 LinkedList,得出結論:尾部刪除資料,ArrayList 效能優於 LinkedList
多次隨機位置刪除資料,ArrayList 吞吐量大於 LinkedList,得出結論:尾部刪除資料,ArrayList 效能優於 LinkedList
綜上,刪除資料操作的結論如下
獲取位置 | 列表長度100w | 列表長度1000w |
---|---|---|
頭部 | LinkedList 優於 ArrayList | LinkedList 優於 ArrayList |
尾部 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
多次隨機位置 | ArrayList 優於 LinkedList | ArrayList 優於 LinkedList |
遍歷資料
分別比較了 5 種遍歷方式在1000、100w、1000w 長度的列表下的效能狀況。
遍歷方式一,原始形式的 for 迴圈
for(int i = 0;i<x;i++){
//do something
}
遍歷方式二,增強形式的 for 迴圈
for(Integer i : arrayList){
// do something
}
遍歷方式三,迭代器方式
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
// do something
}
遍歷方式四,forEach 方式
arrayList.forEach(integer -> {
// do something
});
遍歷方式五,stream 方式
正常來講,stream 後面再接 forEach 是不太正確的測試方式,一般 stream 都會配合 map、filter 等操作來進行篩選、計算等。這裡為了方面測試,姑且先這個寫了。
arrayList.stream().forEach(integer -> {
// do something
});
ArrayList 這 5 種方式遍歷的效能
無論是1w、100w、1000w ,這五種方式的效能排名都是基本穩定的。原始 for 迴圈最優,forEach 方式最差。
LinkedList 這 5 種方式遍歷的效能
無論是1w、100w、1000w ,這五種方式的效能排名都是基本穩定的。和 ArrayList 相似,都是原始 for 迴圈最優,forEach 方式最差,其他三種方式相差不大。
比較這五種方式下 ArrayList 和 LinkedList 的效能
觀察發現,迭代器方式、stream 方式、增強 for 迴圈這三種方式,ArrayList 的效能基本上都要優於 LinkedList,而最原始的 for 迴圈方式,直到列表長度達到了 1000w,仍然是 LinkedList 優於 ArrayList。
LinkedList 只有在頭部插入和刪除資料頻繁的時候才有優勢,但是能找到這種應用場景似乎也不容易找到。如此看來,我們在程式中用到列表就隨手 new 一個 ArrayList 倒也很有道理。
不要吝惜你的「推薦」呦
本文中的測試使用 JMH 基準測試完成的,圖表是用 Excel 做的,如果需要原始碼和 Excel 檔案,請在公眾號內回覆關鍵詞 「jmh」
歡迎關注,不定期更新本系列和其他文章
古時的風箏
,進入公眾號可以加入交流群