在Java中,ArrayList的數據結構爲數組,LinkList數據結構爲鏈表。如果我們學過算法與數據結構,我們可以很快得到以下結論:
對於數組,隨機元素訪問的時間複雜度是 O(1),元素插入操作是 O(n);
對於鏈表,隨機元素訪問的時間複雜度是 O(n),元素插入操作是 O(1)。
因此,我們會認爲在大量的元素插入、很少的隨機訪問的業務場景下,是不是就應該使用 LinkedList 呢。其實不對,通過下面的例子我們來演示一下在不同數據量下,ArrayList和LinkList的隨機訪問和插入的性能。
//LinkedList訪問
private static void linkedListGet(int elementCount, int loopCount) {
List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}
//ArrayList訪問
private static void arrayListGet(int elementCount, int loopCount) {
List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}
//LinkedList插入
private static void linkedListAdd(int elementCount, int loopCount) {
List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}
//ArrayList插入
private static void arrayListAdd(int elementCount, int loopCount) {
List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}
測試代碼如下,10萬個元素,循環10萬次
int elementCount = 100000;
int loopCount = 100000;
StopWatch stopWatch = new StopWatch();
stopWatch.start("linkedListGet");
linkedListGet(elementCount, loopCount);
stopWatch.stop();
stopWatch.start("arrayListGet");
arrayListGet(elementCount, loopCount);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
StopWatch stopWatch2 = new StopWatch();
stopWatch2.start("linkedListAdd");
linkedListAdd(elementCount, loopCount);
stopWatch2.stop();
stopWatch2.start("arrayListAdd");
arrayListAdd(elementCount, loopCount);
stopWatch2.stop();
System.out.println(stopWatch2.prettyPrint());
對於結果,你可能會很奇怪。在隨機訪問方面,我們看到了ArrayList的絕對優勢,而在隨機插入方面,ArrayList也佔據了巨大的優勢:
10W:
StopWatch '': running time (millis) = 4520
-----------------------------------------
ms % Task name
-----------------------------------------
04509 100% linkedListGet
00011 000% arrayListGet
StopWatch '': running time (millis) = 28404
-----------------------------------------
ms % Task name
-----------------------------------------
26570 094% linkedListAdd
01834 006% arrayListAdd
我還測試了20W,50W,100W的數據量和操作量下的結果
20W:
StopWatch '': running time (millis) = 23972
-----------------------------------------
ms % Task name
-----------------------------------------
23955 100% linkedListGet
00017 000% arrayListGet
StopWatch '': running time (millis) = 157245
-----------------------------------------
ms % Task name
-----------------------------------------
149343 095% linkedListAdd
07902 005% arrayListAdd
50W :StopWatch '': running time (millis) = 175061
-----------------------------------------
ms % Task name
-----------------------------------------
175020 100% linkedListGet
00041 000% arrayListGet
StopWatch '': running time (millis) = 2164864
-----------------------------------------
ms % Task name
-----------------------------------------
2115619 098% linkedListAdd
49245 002% arrayListAdd
100W:
StopWatch '': running time (millis) = 721593
-----------------------------------------
ms % Task name
-----------------------------------------
721508 100% linkedListGet
00085 000% arrayListGet
StopWatch '': running time (millis) = 8900864
-----------------------------------------
ms % Task name
-----------------------------------------
8646038 097% linkedListAdd
254826 003% arrayListAdd
爲什麼會出現這種情況呢?我們查看源碼可以發現,雖然你插入的時間複雜度爲O(1),但是在插入之前你要先找到這個節點,這個操作是通過遍歷來實現的。因此,對於插入,LinkList的時間複雜度也是O(n),不能只考慮插入操作的成本。而且,由於CPU緩存和內存連續性等問題,鏈表這種數據結構的實現方式對性能並不友好,即使在它最擅長的場景都不一定可以發揮威力。