實驗
jdk版本1.8,測試平臺mbp2016
在資料尾部插入資料
測試程式碼
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList<String> links = new LinkedList<>();
int len = 6553631;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.add("sss");
}
long etime = System.currentTimeMillis();
log.info("耗時:{},大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList<String> arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add("sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗時:{},大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
複製程式碼
測試結果
此時我們的數量級別是百萬級別,我們驚訝的發現ArrayList插入效率要比LinkedList快接近20倍,為什麼?why?我們明明記得在學習java集合的時候,明確的知道是ArrayList查詢快,增刪慢的,LinkedList的特細則與之相反的,可是現實測試卻跟定義不一樣呢,那我們在多做點測試,改變數量級別看看十萬,萬,千級別的測試結果,我們改變插入集合的資料量大小測試。首先降低到十萬級別 發現測試的插入效率已經很接近了
萬級別 這時候我們發現和十萬級別的測試結果差不多,還是ArrayList插入快一點
接著我們降低到千級別 此時兩者的效率差不多,還是沒有體現出書中定義的2者關於插入效率的問題,問題還是沒有得到解決,那麼我們思考一下,上面的實驗我們都是預設插入的末尾的,是否跟我麼插入的順序有關係嗎,我們下面測試一下將資料插入的頭部
在陣列頭部插入資料
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList<String> links = new LinkedList<>();
int len = 100000;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.addFirst("sss");
}
long etime = System.currentTimeMillis();
log.info("耗時:{},LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList<String> arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add(0,"sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗時:{},ArrayList大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
複製程式碼
此時我們發現,在萬級別LinkedList的插入效能就看出來了
此時我們發現,在十萬級別LinkedList的插入效能是ArrayList的100+倍,那麼我們下面再測試一下在陣列中間部分插入資料
在陣列中間插入資料
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList<String> links = new LinkedList<>();
int len = 10000;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.add(links.size() / 2, "sss");
}
long etime = System.currentTimeMillis();
log.info("耗時:{},LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList<String> arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add(arrays.size() / 2, "sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗時:{},ArrayList大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
複製程式碼
資料量萬級別Linkedlist插入效率比ArrayList慢20倍
資料量十萬級別Linkedlist插入效率比ArrayList慢160+倍下面讓我係統測試一下其他數量級資料,並且加上隨機插入測試
public static void test(int listSize, int type) {
LinkedList<String> links = new LinkedList<>();
log.info("陣列長度:{},插入方式{}", listSize, type);
long btime = System.currentTimeMillis();
for (int i = 0; i < listSize; i++) {
switch (type) {
case 0:
links.addFirst("測試資料");
break;
case 1:
links.add(links.size() / 2, "測試資料");
break;
case 2:
links.addLast("測試資料");
break;
default:
Random random = new Random();
int rd = random.nextInt(links.size()+1);
links.add(rd, "測試資料");
}
}
long etime = System.currentTimeMillis();
log.info("耗時:{}ms,LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList<String> arrays = new ArrayList<>();
for (int i = 0; i < listSize; i++) {
switch (type) {
case 0:
arrays.add(0, "測試資料");
break;
case 1:
arrays.add(arrays.size() / 2, "測試資料");
break;
case 2:
arrays.add("測試資料");
break;
default:
Random random = new Random();
int rd = random.nextInt(arrays.size()+1);
arrays.add(rd, "測試資料");
}
}
long etime1 = System.currentTimeMillis();
log.info("耗時:{}ms,ArrayList大小:{}", etime1 - btime1, arrays.size());
}
複製程式碼
測試結果
-
在集合頭部插入實驗資料
-
在集合中間插入實驗資料
-
在集合尾部插入實驗資料
-
在集合隨機位置插入資料
原始碼分析
在指定位置插入
LinkedList原始碼
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
複製程式碼
- 判斷是否超過連結串列長度,超過則拋錯誤
- 如果是插入最後的位置直接使用linkLast方法而不必去遍歷查詢對應位置
- node方法尋找index所指向的Node,首先判斷index是否大於size/2,大於則從末尾往前找,小於則從0開始往後找
- 找到之後就是new一個node物件,設定指標的問題
LinkedList:效能主要在於遍歷連結串列查詢index
ArrayList原始碼
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
複製程式碼
- rangeCheckForAdd判斷index是否合法
- ensureCapacityInternal判斷是否需要擴容
- arraycopy陣列複製,從index開始把後面的陣列元素全部複製到相對後一位的位置,該方法是native方法而且是連續記憶體複製問題,因此效能影響也沒想象中的大
- elementData將element賦值給陣列index元素
ArrayList:影響ArrayList效能的主要因素是擴容和陣列複製,但是當size很大時陣列擴容影響就會變小,那麼此時的效率就會提升,此時如果在中間部分插入資料時候,我們要插入的位置為i,陣列長度是n,那麼就要變動i之後的n-i的資料。
在頭部部插入
LinkedList原始碼
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
複製程式碼
可以看到LinkedList直接在頭部時候不必遍歷,所以效率很高,體現了LinkedList的插入效率高的特性
ArrayList原始碼解釋同(指定位置插入)
在尾部插入
LinkedList原始碼
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
複製程式碼
LinkedList:用傳入的值new一個Node物件,然後尾指標指向該新的Node
ArrayList原始碼
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
複製程式碼
ArrayList:如果超出容量需要擴容,不需擴容時直接陣列元素賦值
結論:當資料量越來越大時,ArrayList比LinkedList快
原因:當資料量大時,ArrayList每次擴容都能得到很大的新空間,解決了前期頻繁擴容的劣勢,而LinkedList雖然有尾指標,但是每次add都要將物件new成一個Node(而ArrayList直接陣列對應位置元素賦值)
結論
資料量\插入位置 | 頭部 | 中間 | 尾部 | 隨機 |
---|---|---|---|---|
百 | 效率持平 | 效率持平 | 效率持平 | 效率持平 |
千 | LinkedList插入快 | 效率持平 | 效率持平 | ArrayList插入快 |
萬 | LinkedList插入快 | ArrayList插入快 | 效率持平 | ArrayList插入快 |
十萬 | LinkedList插入快 | ArrayList插入快 | ArrayList插入快 | ArrayList插入快 |
百萬 | LinkedList插入快 | ArrayList插入快 | ArrayList插入快 | ArrayList插入快 |
- 在尾部插入資料,資料量較小時LinkedList比較快,因為ArrayList要頻繁擴容,當資料量大時ArrayList比較快,因為ArrayList擴容是當前容量*1.5,大容量擴容一次就能提供很多空間,當ArrayList不需擴容時效率明顯比LinkedList高,因為直接陣列元素賦值不需new Node
- 在首部插入資料,LinkedList較快,因為LinkedList遍歷插入位置花費時間很小,而ArrayList需要將原陣列所有元素進行一次System.arraycopy
- 插入位置越往中間,LinkedList效率越低,因為它遍歷獲取插入位置是從兩頭往中間搜,index越往中間遍歷越久,因此ArrayList的插入效率可能會比LinkedList高
- 插入位置越往後,ArrayList效率越高,因為陣列需要複製後移的資料少了,那麼System.arraycopy就快了,因此在首部插入資料LinkedList效率比ArrayList高,尾部插入資料ArrayList效率比LinkedList高
- inkedList可以實現佇列,棧等資料結構,這是它的優勢