【发布时间】:2020-12-26 23:36:07
【问题描述】:
最近我一直在寻找 Java 中的 LinkedLists。经过一些研究,我发现使用 LinkedList 而不是 ArrayList 的主要好处是,在具有大量数据的列表中删除/添加中间数组元素时速度更快。因此,使用 ArrayList 而不是始终使用 LinkedList 有什么好处?
为了比较,我写了这个简单的程序来比较LinkedList和ArrayList的运行速度。该程序的概念是它创建一个长度为 n 的 int 数组,并用从 Integer.MIN_VALUE 到 Integer.MAX_VALUE 的随机整数填充它。
然后,它创建另一个数组,该数组是第一个数组元素的 x%。例如,如果第一个数组是[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],那么第二个数组,如果 x 设置为 50%,可能是[2, 4, 5, 9, 10]。这将产生一个数组,其中包含原始数组的开头、中间和结尾的随机元素。
不计算在创建这些数组期间经过的运行时间。之后,程序创建一个 LinkedList,将第一个数组中的所有元素添加到该列表中,并从该列表中删除第二个数组的元素。我重复此过程 y 次,并使用 System.nanoTime() 计算平均经过时间。
我知道我的代码远没有经过优化,但由于它对于 LinkedList 和 ArrayList完全相同的代码,只更改了正在创建的列表的类型并且不在初始化时预先设置列表的大小,结果应该不会受到影响,对吗?例如,如果我的错误代码使其运行速度慢了 400%,因为两者都使用相同的代码,结果不应该受到任何影响,因为理论上两者都慢了 400%。
我发现 ArrayLists 平均比 LinkedLists 快 360%,至少在使用 10 万个整数数组并运行 10 次同时删除 45% 的元素时是这样。
根据我阅读this、this 和this StackOverflow 帖子的理解,这些帖子比较了 ArrayList 和 LinkedList 的性能,这是因为 LinkedLists 为每个节点分配了更多内存(24 字节与 ArrayList 上的 4 字节) .虽然理论上,LinkedLists 在添加/删除数组元素中的元素时更快,平均只需要 O(n/4) 步骤,而 Lists 需要 O(n/2),实际上——至少在我的测试和上面链接的帖子中执行的测试中——这似乎不是真的。如果是这样,那么问题就反过来了:使用 LinkedLists 而不是 ArrayList 有什么好处?才可以使用list.iterator()?
这是我得到的确切结果和我用来得到它们的代码:
It took 53079,970300 ms with LinkedList to run 10 times with an array of 100000 integers removing 45 percent of the elements
The average run time for LinkedList was 5307,997030 ms
It took 14344,833900 ms with normal List to run 10 times with an array of 100000 integers removing 45 percent of the elements
The average run time for normal List was 1434,483390 ms
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class LinkedLists {
public static void main(String[] args) {
int timesToRun = 10;
int percentageToRemove = 45;
int[] arr = getRandomArray(100_000);
int[] integersToRemoveArr = getIntegersToRemoveArr(percentageToRemove, arr);
long elapsedTime = 0L;
long startTime;
long endTime;
//LinkedLists
startTime = System.nanoTime();
for (int i = 0; i < timesToRun; i++) {
linkedList(arr, integersToRemoveArr);
}
endTime = System.nanoTime();
elapsedTime += endTime - startTime;
System.out.printf("It took %f ms with LinkedList to run %d times with an array of %d integers removing %d percent of the elements\n", (endTime - startTime) * 0.000001, timesToRun, arr.length, percentageToRemove);
System.out.printf("The average run time for LinkedList was %f ms\n", elapsedTime/timesToRun * 0.000001);
//Force GC - again, not sure I need this, just to be safe
Runtime.getRuntime().gc();
//Normal Lists
elapsedTime = 0L;
startTime = System.nanoTime();
for (int i = 0; i < timesToRun; i++) {
normalList(arr, integersToRemoveArr);
}
endTime = System.nanoTime();
elapsedTime += endTime - startTime;
System.out.printf("It took %f ms with normal List to run %d times with an array of %d integers removing %d percent of the elements\n", (endTime - startTime) * 0.000001, timesToRun, arr.length, percentageToRemove);
System.out.printf("The average run time for normal List was %f ms\n", elapsedTime/timesToRun * 0.000001);
}
/**
* Get an array wit X percent of the integers of the providade array
*/
public static int[] getIntegersToRemoveArr(int percentageToRemove, int[] originalArray) {
if (percentageToRemove > 100 || percentageToRemove < 0) {
return new int[]{};
}
List<Integer> indexesToRemove = new ArrayList<>();
while (indexesToRemove.size() < (originalArray.length * (percentageToRemove)/100F)) {
int x = ThreadLocalRandom.current().nextInt(0, originalArray.length-1);
indexesToRemove.add(originalArray[x]);
}
int[] arr = new int[indexesToRemove.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = indexesToRemove.get(i);
}
Arrays.sort(arr);
return arr;
}
public static int[] getRandomArray(int length) {
if (length < 1) {
return new int[]{};
}
int[] arr = new int[length];
for (int i = 0; i < length; i++) {
arr[i] = ThreadLocalRandom.current().nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
}
return arr;
}
/**
* Create a LinkedList from the provided array and remove from the list the elements in integersToRemove
*/
public static void linkedList(int[] arr, int[] integersToRemove) {
LinkedList<Integer> list = new LinkedList<>();
for (int i : arr) {
list.add(i);
}
for (int i : integersToRemove) {
list.remove((Integer) i);
}
//Nulling the element to allow GC to remove it - i'm not sure this is needed since I'm not using
//this var anywhere, but just to be safe
list = null;
}
/**
* Create a LinkedList from the provided array and remove from the list the elements in integersToRemove
*/
public static void normalList(int[] arr, int[] integersToRemove) {
List<Integer> list = new ArrayList<>();
for (int i : arr) {
list.add(i);
}
for (int i : integersToRemove) {
list.remove((Integer) i);
}
//Nulling the element to allow GC to remove it - i'm not sure this is needed since I'm not using
//this var anywhere, but just to be safe
list = null;
}
}
【问题讨论】:
-
几乎总是
ArrayList严格优于LinkedList。LinkedList的存在主要是为了完整性,并且在极少数情况下更好。 Not even Joshua Bloch uses it(这就是写它的人)。
标签: java performance arraylist linked-list