Java中ArrayList和LinkedList之间的区别 - 性能的原因

时间:2013-03-01 03:37:46

标签: java performance collections arraylist linked-list

我认为理解上我理解ArrayList和LinkedList之间的区别很好。然而,这是第一次,我把它做了一点测试,测试结果出来了,与我的期望完全不同。

期望:

  1. 插入时,Arraylist比LinkedList慢 开始,因为它必须“转移”元素,对于链表,它 只更新2个参考文献。

    现实:在大多数迭代中都是相同的。少数选择 迭代,它更慢。

  2. 在开头删除时,Arraylist会比LinkedList慢,因为它必须“移位”元素,对于Linkedlist,它只是使一个元素无效。

    现实:从beg中删除时性能相同。

  3. 测试用例:1,000,000个元素

    public static void main(String[] args) {
        int n = 1000000;
    
        List arrayList = new ArrayList(n+10);
        long milis = System.currentTimeMillis();
        for(int i= 0 ;i<n;i++){
            arrayList.add(i);
        }
        System.out.println("insert arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        List linkedList = new LinkedList();
        milis = System.currentTimeMillis();
        for(int i= 0 ;i<n;i++){
            linkedList.add(i);
        }
        System.out.println("insert linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        //System.out.println("Adding at end");
        milis = System.currentTimeMillis();
        arrayList.add(n-5,n+1);
        System.out.println("APPEND arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.add(n-5,n+1);
        System.out.println("APPEND linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        //add at front
        milis = System.currentTimeMillis();
        arrayList.add(0,0);
        System.out.println("INSERT BEG arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.add(0,0);
        System.out.println("INSERT BEG linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        //add at middle
        milis = System.currentTimeMillis();
        arrayList.add(n/2,n/2);
        System.out.println("INSERT MIDDLE arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.add(n/2,n/2);
        System.out.println("INSERT MIDDLE linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        //get from front, mid, end
        milis = System.currentTimeMillis();
        arrayList.get(0);
        System.out.println("get front arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.get(0);
        System.out.println("get front linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        arrayList.get(n/2);
        System.out.println("get MIDDLE arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.get(n/2);
        System.out.println("get MIDDLE linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        arrayList.get(n-4);
        System.out.println("get last arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.get(n-4);
        System.out.println("get last linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        //delete from front, mid, end.
        milis = System.currentTimeMillis();
        arrayList.remove(0);
        System.out.println("del front arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.remove(0);
        System.out.println("del front linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        arrayList.remove(n/2);
        System.out.println("del mid arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.remove(n/2);
        System.out.println("del mid linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        arrayList.remove(n-4);
        System.out.println("del end arraylist takes "+(System.currentTimeMillis()-milis)+" ms");
    
        milis = System.currentTimeMillis();
        linkedList.remove(n-4);
        System.out.println("del end linkedlist takes "+(System.currentTimeMillis()-milis)+" ms");
    
    }
    

    输出记录

    insert arraylist takes 141 ms
    insert linkedlist takes 312 ms
    APPEND arraylist takes 0 ms
    APPEND linkedlist takes 0 ms
    INSERT BEG arraylist takes 0 ms
    INSERT BEG linkedlist takes 0 ms
    INSERT MIDDLE arraylist takes 0 ms
    INSERT MIDDLE linkedlist takes 0 ms
    get front arraylist takes 0 ms
    get front linkedlist takes 0 ms
    get MIDDLE arraylist takes 0 ms
    get MIDDLE linkedlist takes 16 ms
    get last arraylist takes 0 ms
    get last linkedlist takes 0 ms
    del front arraylist takes 0 ms
    del front linkedlist takes 0 ms
    del mid arraylist takes 0 ms
    del mid linkedlist takes 15 ms
    del end arraylist takes 0 ms
    del end linkedlist takes 0 ms
    

    那是什么原因?使用JDK 1.6。

    编辑:使用System.nanotime()之后,它确实给了我预期的答案。同意,它只是一次试验,应该平均。

    insert arraylist takes 137076082 ns
    insert linkdlist takes 318985917 ns
    APPEND arraylist takes 69751 ns
    APPEND linkdlist takes 98126 ns
    **INSERT BEG arraylist takes 2027764 ns
    INSERT BEG linkdlist takes 53522 ns**
    INSERT MIDDLE arraylist takes 1008253 ns
    INSERT MIDDLE linkdlist takes 10395846 ns
    get front arraylist takes 42364 ns
    get front linkdlist takes 77473 ns
    get MIDDLE arraylist takes 39499 ns
    get MIDDLE linkdlist takes 9645996 ns
    get last arraylist takes 46165 ns
    get last linkdlist takes 43446 ns
    **del front arraylist takes 1720329 ns
    del front linkdlist takes 108063 ns**
    del mid arraylist takes 1157398 ns
    del mid linkdlist takes 11845077 ns
    del end arraylist takes 54149 ns
    del end linkdlist takes 49744 ns
    

3 个答案:

答案 0 :(得分:6)

前两个(奇怪的)测试编号的解释是:

插入ArrayList通常较慢,因为一旦你到达它的边界就必须增长。它必须创建一个新的更大的数组,并从原始数组中复制数据。

但是当你创建一个已经很大足以容纳所有插入的ArrayList时(这是你的情况,因为你正在做new ArrayList(n+10)) - 它显然不会涉及任何数组复制操作。添加到它将比使用LinkedList更快,因为LinkedList必须处理它的“链接”(指针),而巨大的ArrayList只是在给定(最后)索引处设置值。

此外,您的测试并不好,因为每次迭代都涉及自动装箱(从int转换为整数) - 这将花费额外的时间来做到这一点,并且还会因为Integers cache将填满结果而搞砸在第一关。

答案 1 :(得分:2)

int n = 1000000;太小了。你得到0 ms并不意味着它没有时间来完成插入或删除。这意味着经过的时间不到1毫秒。尝试提高int n = 1000000;的数量。你可以看到差异。

编辑:我误读了你的代码。我以为你在数组列表的前面插入了n个元素。你绝对应该插入多个项目而不是仅插入一个项目。

另一个编辑:如果您插入n元素,则无需提高n的值。相反,您可能希望减少它,因为在ArrayList的前面插入是一个缓慢的操作。

答案 2 :(得分:0)

使用ArrayList,中间操作的插入将是理论上的两个步骤哦:

  • O(1)找到位置并插入新值
  • O(n)将剩余值向前移动。

使用LinkedList,如果尚未知道相关节点(从头节点循环),则还有两个步骤:

  • O(n)在位置
  • 处找到节点
  • O(1)插入新值

然而,除了预期的算法复杂性之外,还有其他一些因素会对实际运行时产生影响:

  • 特定语言实现可能会使特定进程的某些部分更快。在这种情况下,ArrayList使用的Java的arraycopy()接口比复制每个值的for循环更快。对于所有数组(大小,类型)而言可能并非如此 - 同样,它将是特定于实现的。

  • Big Oh忽略可能对某些数据集产生影响的常量。例如,冒泡排序是O(n * n)但可以检测O(n)中的预排序数组,并且对于几乎排序的数组非常快。

  • LinkedList需要向虚拟机请求更多内存(创建Node对象)。需要更多内存的进程有时会导致瓶颈。

总结:谨防理论假设并始终衡量,最好是用现实世界的数据和操作:)。