概述
- ArrayList 是一個動態數組,它是線程不安全的,允許元素為null。其底層數據結構依然是數組,因為實現了RandomAccess接口,所以擁有隨機快速訪問的能力,ArrayList可以以O(1)的時間復雜度去根據下標訪問元素。由於數組的內存連續,可以根據下標以O1的時間改查元素,因此時間效率很高。
- LinkedList 是一個雙向鏈表,它是 線程不安全的,允許元素為null。其底層數據結構是鏈表,和ArrayList比,沒有實現RandomAccess接口,所以其以下標,隨機訪問元素速度較慢。但它的增刪只需要移動指針即可,故時間效率較高。
總體而言,ArrayList的改查效率高,LinkedList的增刪效率高,真實情況下是否如此呢?
ArrayList 、LinkedList效率對比
1.插入
/**
* 順序插
*/
@Test
public void test() {
ArrayList<String> arr = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
arr.add("a");
}
long end = System.currentTimeMillis();
System.out.println("arrylist time:" + (end - start));
LinkedList<String> link = new LinkedList<>();
start = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
link.addLast("a");
}
end = System.currentTimeMillis();
System.out.println("linkedlist time:" + (end - start));
}
可以發現,結果與我們預計的相同。LinkedList插入速度快於ArrayList。
但是,我們將插入的數值加大到100w。
ArrayList的插入速度居然快於LinkedList,這是為什么呢?
因為ArrayList每次需要擴容的話,新數組是舊數組容量的1.5倍,再使用Arrays.copyOf()將舊數組中的元素復制到新數組,不需要擴容的話則直接插入。插入數據的大部分時間用來復制數組。
而LinkedList每次添加元素需要構建節點,再將新節點插入隊尾。插入數據的大部分時間用來構建節點。
當元素很少的時候,構建節點所需的時間少於數組復制的時間,但是當元素急劇增多的時候,ArrayList數組擴容到足夠大,已經足夠添加所有元素,此時不需要再次擴容,所以幾乎沒有復制數組的時間。而LinkedList鏈表添加時間隨着元素的不斷增多而增多。
讓我們再測試各個位置添加元素的效率
/**
* 頭插入 array < linked
*/
@Test
public void test1() {
ArrayList<String> arr = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
arr.add(0, "a");
}
long end = System.currentTimeMillis();
System.out.println("arrylist time:" + (end - start));
LinkedList<String> link = new LinkedList<>();
start = System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
link.add(0, "a");
}
end = System.currentTimeMillis();
System.out.println("linkedlist time:" + (end - start));
}
這是因為ArrayList每次插入新元素都要將后面所有元素向后移動一位,這是ArrayList插入最壞的情況。
/**
* 中間插 ArrayList 》》 LinkedList
*/
@Test
public void test3() {
ArrayList<String> arr = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
arr.add("a");
}
LinkedList<String> link = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
link.add("a");
}
int n = arr.size() / 2;
long startlist = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
arr.add(n + i, "a");
}
long endlist = System.currentTimeMillis();
System.out.println("arr time===" + (endlist - startlist));
n = link.size() / 2;
long startLink = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
link.add(n + i, "a");
}
long endLink = System.currentTimeMillis();
System.out.println("link time -----" + (endLink - startLink));
}
可以看到只是插入萬級別的數據,差距已經很大了。
LinkedList在指定位置插入需要先移動移動指針到指定位置,雖然將鏈表分成前后兩部分進行查找,但是速度明顯慢於數組,這在隨機插入中表現更加明顯。
/**
* 隨機插入 array >> linked
*/
@Test
public void test4() {
List<String> list = new ArrayList<>();
Random random = new Random();
long start = System.currentTimeMillis();
for (int i = 1; i < 50000; i++) {
int x = random.nextInt(i);
list.add(x, "a");
}
long end = System.currentTimeMillis();
System.out.println("arrayList insert time " + (end - start));
List<String> list1 = new LinkedList<>();
start = System.currentTimeMillis();
for (int i = 1; i < 50000; i++) {
int x = random.nextInt(i);
list1.add(x, "a");
}
end = System.currentTimeMillis();
System.out.println("linkedList insert time " + (end - start));
}
只是插入5w數據,差距已經如此明顯。
綜上所述,除了經常進行頭插入的情況,一般情況下插入優先考慮使用ArrayList。
2.讀取
ArrayList擁有快速訪問能力,不管是隨機讀還是順序讀,效率都高於LinkedList。
/**
* 隨機讀 array > linked
*/
@Test
public void test6() {
List<String> arr = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 200000; i++) {
arr.add("a");
}
List<String> linked = new LinkedList<>();
for (int i = 0; i < 200000; i++) {
linked.add("a");
}
long start = System.currentTimeMillis();
for (int i = 1; i < arr.size(); i++) {
int x = random.nextInt(i);
arr.get(x);
}
long end = System.currentTimeMillis();
System.out.println("arrayList insert time " + (end - start));
start = System.currentTimeMillis();
for (int i = 1; i < linked.size(); i++) {
int x = random.nextInt(i);
linked.get(i);
}
end = System.currentTimeMillis();
System.out.println("linkedList insert time " + (end - start));
}
/**
* 順序刪 array 》 linked
*/
@Test
public void test7() {
ArrayList<String> arr = new ArrayList<>();
for (int i = 0; i < 20000; i++) {
arr.add("a");
}
long start = System.currentTimeMillis();
for (int i = 0; i < arr.size(); i++) {
arr.remove(i);
}
long end = System.currentTimeMillis();
System.out.println("arrylist time:" + (end - start));
LinkedList<String> link = new LinkedList<>();
for (int i = 0; i < 20000; i++) {
link.add("a");
}
start = System.currentTimeMillis();
for (int i = 0; i < link.size(); i++) {
link.remove(i);
}
end = System.currentTimeMillis();
System.out.println("linkedlist time:" + (end - start));
}