java - ArrayList 与 LinkedList

标签 java data-structures collections arraylist linked-list

我关注的是 previous post上面写着:

For LinkedList

  • get is O(n)
  • add is O(1)
  • remove is O(n)
  • Iterator.remove is O(1)

For ArrayList

  • get is O(1)
  • add is O(1) amortized, but O(n) worst-case since the array must be resized and copied
  • remove is O(n)

所以通过查看这个,我得出结论,如果我只需要在我的集合中按顺序插入 5000000 个元素,LinkedList 将超越 ArrayList

如果我只是通过迭代从集合中获取元素,即不抓取中间的元素,仍然 LinkedList 将超越 ArrayList

现在为了验证我上面的两个陈述,我写了下面的示例程序......但我很惊讶我上面的陈述被证明是错误的。

ArrayList 在这两种情况下都优于 Linkedlist。从集合中添加和获取它们比 LinkedList 花费的时间更少。有什么我做错了吗,或者关于 LinkedListArrayList 的初始语句对于大小为 5000000 的集合不适用?

我提到了大小,因为如果我将元素数量减少到 50000 个,LinkedList 的性能会更好,并且初始语句成立。

long nano1 = System.nanoTime();

List<Integer> arr = new ArrayList();
for (int i = 0; i < 5000000; i++) {
    arr.add(i);
}
System.out.println(System.nanoTime() - nano1);

for (int j : arr) {
    // Do nothing
}
System.out.println(System.nanoTime() - nano1);

long nano2 = System.nanoTime();

List<Integer> arrL = new LinkedList();
for (int i = 0; i < 5000000; i++) {
    arrL.add(i);
}
System.out.println(System.nanoTime() - nano2);

for (int j : arrL) {
    // Do nothing
}
System.out.println(System.nanoTime() - nano2);

最佳答案

请记住,大 O 复杂度描述的是渐近行为,可能无法反射(reflect)实际的实现速度。它描述了每个操作的成本如何随着列表的大小而不是每个操作的速度而增长。例如,以下 add 的实现是 O(1) 但并不快:

public class MyList extends LinkedList {
    public void add(Object o) {
        Thread.sleep(10000);
        super.add(o);
    }
}

我怀疑在您的情况下 ArrayList 表现良好,因为它相当积极地增加了它的内部缓冲区大小,因此不会有大量的重新分配。当缓冲区不需要调整大小时,ArrayList 会有更快的 adds.

在进行此类分析时,您还需要非常小心。我建议您更改分析代码以进行预热阶段(因此 JIT 有机会在不影响您的结果的情况下进行一些优化)并在多次运行中平均结果。

private final static int WARMUP = 1000;
private final static int TEST = 1000;
private final static int SIZE = 500000;

public void perfTest() {
    // Warmup
    for (int i = 0; i < WARMUP; ++i) {
        buildArrayList();
    }
    // Test
    long sum = 0;
    for (int i = 0; i < TEST; ++i) {
        sum += buildArrayList();
    }
    System.out.println("Average time to build array list: " + (sum / TEST));
}

public long buildArrayList() {
    long start = System.nanoTime();
    ArrayList a = new ArrayList();
    for (int i = 0; i < SIZE; ++i) {
        a.add(i);
    }
    long end = System.nanoTime();
    return end - start;
}

... same for buildLinkedList

(注意 sum 可能会溢出,最好使用 System.currentTimeMillis())。

编译器也有可能优化掉你的空 get 循环。确保循环确实做了一些事情来确保调用正确的代码。

关于java - ArrayList 与 LinkedList,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5846183/

相关文章:

java - 如何检查当前日期是否是本月的第一天

c++ - 当我们已经拥有更强大的 vector 时,为什么还需要堆栈?

c++ - 当我编译并准备推送元素时,出现段错误

collections - groovy:更改列表的每个元素并加入

java - 为什么在 Set/List/Map 中包装元素的方法在名称中包含 'singleton'?(java.util.Collections)

java - 执行操作系统相关命令

java - 附加到另一个类的对象?

data-structures - 列表和多重集有什么区别?

java - 故障安全迭代器和弱一致性迭代器

java - 我们如何将网页表单 html 模板与用户输入的数据一起存储在任何服务器中?