ArrayList和LinkedList之间的性能差异

时间:2012-05-18 16:35:59

标签: java arraylist doubly-linked-list

是的,这是一个古老的话题,但我仍有一些困惑。

在Java中,人们说:

  1. 如果我随机访问其元素,ArrayList比LinkedList更快。我认为随机访问意味着“给我第n个元素”。为什么ArrayList更快?

  2. LinkedList比ArrayList更快删除。我理解这个。由于需要重新分配内部备份阵列,因此ArrayList较慢。代码说明:

    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.remove("b");
    System.out.println(list.get(1)); //output "c"
    
  3. LinkedList比ArrayList更快插入。插入意味着什么?如果它意味着将一些元素移回然后将元素放在中间空白点,则ArrayList应该比LinkedList慢。如果插入仅意味着添加(对象)操作,那么这怎么可能很慢?

11 个答案:

答案 0 :(得分:62)

  

如果我随机访问其元素,ArrayList比LinkedList更快。我认为随机访问意味着“给我第n个元素”。为什么ArrayList更快?

ArrayList直接引用列表中的每个元素,因此它可以在恒定时间内获得第n个元素。 LinkedList必须从头开始遍历列表才能到达第n个元素。

  

LinkedList比ArrayList更快删除。我理解这个。由于需要重新分配内部备份阵列,因此ArrayList较慢。

ArrayList速度较慢,因为它需要复制部分数组才能删除已经空闲的插槽。如果使用ListIterator.remove() API完成删除,则LinkedList只需操作一些引用;如果删除是通过值或索引完成的,LinkedList必须首先扫描整个列表以找到要删除的元素。

  

如果这意味着将一些元素移回然后将元素放在中间空白点,则ArrayList应该更慢。

是的,这就是它的含义。 ArrayList确实比LinkedList慢,因为它必须释放数组中间的一个插槽。这涉及移动一些引用,并在最坏的情况下重新分配整个数组。 LinkedList只需操纵一些参考文献。

答案 1 :(得分:22)

暂时忽略此答案。其他答案,特别是 aix 的答案,大多是正确的。从长远来看,他们是下注的方式。如果你有足够的数据(在一台机器上的一个基准测试,似乎大约有一百万个条目),ArrayList和LinkedList目前可以像广告一样工作。但是,在21世纪初,有一些优点可以应用。

通过我的测试,现代计算机技术似乎给阵列带来了巨大的优势。可以以疯狂的速度移动和复制阵列的元素。因此,在大多数实际情况中,数组和ArrayList在插入和删除时的表现通常会非常显着。换句话说,ArrayList将在自己的游戏中击败LinkedList。

ArrayList的缺点是它会在删除后挂起到内存空间,其中LinkedList在放弃条目时会占用空间。

数组和ArrayList的更大的缺点是它们碎片释放内存并使垃圾收集器过度工作。随着ArrayList的扩展,它会创建新的更大的数组,将旧数组复制到新数组,并释放旧数组。内存充满了大量连续的可用内存块,这些内存对于下一次分配来说还不够大。最终没有适合该分配的空间。尽管90%的内存都是免费的,但没有一个单独的内容足以完成这项工作。 GC会疯狂地移动,但如果重新排列空间需要很长时间,它将抛出OutOfMemoryException。如果它没有放弃,它仍然可以减慢程序的速度。

最糟糕的是这个问题很难预测。您的程序将运行一次正常。然后,如果没有可用的内存,没有任何警告,它会减慢或停止。

LinkedList使用小巧精致的内存,GC喜欢它。当你使用99%的可用内存时它仍然运行良好。

因此,通常情况下,将ArrayList用于较小的数据集,这些数据集可能不会删除大部分内容,或者您​​可以严格控制创建和增长。 (例如,创建一个使用90%内存并使用它而不在程序期间填充它的ArrayList就可以了。继续创建和释放使用10%内存的ArrayList实例会杀死你。)否则,请使用LinkedList (如果您需要随机访问,可以使用某种地图)。如果您有非常大的集合(比如超过100,000个元素),不关心GC,并计划大量插入和删除以及无随机访问,请运行一些基准测试以查看最快的内容。

答案 2 :(得分:13)

ArrayList类是数组的包装类。它包含一个内部数组。

public ArrayList<T> {
    private Object[] array;
    private int size;
}

LinkedList是链表的包装类,带有用于管理数据的内部节点。

public LinkedList<T> {
    class Node<T> {
        T data;
        Node next;
        Node prev;
    }
    private Node<T> first;
    private Node<T> last;
    private int size;
}

注意,本代码用于显示类的可能性,而不是实际的实现。知道实施的可能性,我们可以做进一步的分析:

  

如果我随机访问其元素,ArrayList比LinkedList更快。我认为随机访问意味着“给我第n个元素”。为什么ArrayList更快?

ArrayList的访问时间:O(1)。 LinkedList的访问时间:O(n)。

在数组中,您可以使用array[index]访问任何元素,而在链接列表中,您必须浏览从first开始的所有列表,直到获得所需的元素。

  

LinkedList比ArrayList更快删除。我理解这个。由于需要重新分配内部备份数组,因此ArrayList较慢。

ArrayList的删除时间:访问时间+ O(n)。 LinkedList的删除时间:访问时间+ O(1)。

ArrayList必须将所有元素从array[index]移动到array[index-1],从要删除索引的项开始。 LinkedList应该导航到该项目,然后通过将其与列表分离来擦除该节点。

  

LinkedList比ArrayList更快删除。我理解这个。由于需要重新分配内部备份数组,因此ArrayList较慢。

ArrayList的插入时间:O(n)。 LinkedList的插入时间:O(1)。

为什么ArrayList可以采用O(n)?因为当您插入新元素并且数组已满时,您需要创建一个具有更大尺寸的新数组(您可以使用类似2 * size或3 * size / 2的公式计算新大小)。 LinkedList只是在最后一个旁边添加一个新节点。

这种分析不仅适用于Java,还适用于其他编程语言,如C,C ++和C#。

更多信息:

答案 3 :(得分:4)

对于ArrayLists和LinkedLists,remove()和insert()都具有O(n)的运行时效率。然而,线性处理时间背后的原因有两个非常不同的原因:

在ArrayList中,您可以访问O(1)中的元素,但实际上删除或插入某些内容会使其成为O(n),因为需要更改以下所有元素。

在LinkedList中,实际获取所需元素需要O(n),因为我们必须从最开始直到达到所需的索引。一旦我们到达那里,删除或插入是不变的,因为我们只需要为remove()更改1个引用,为insert()更改2个引用。

两者中哪一个更快插入和移除取决于它发生的位置。如果我们离开头更近,LinkedList会更快,因为我们必须经历相对较少的元素。如果我们接近结束,ArrayList将更快,因为我们在恒定时间到达那里,只需要更改其后的几个剩余元素。

Bonus:虽然没有办法为ArrayList创建这两个方法O(1),但实际上有一种方法可以在LinkedLists中执行此操作。让我们说我们想要通过整个List删除和插入元素的方式。通常你会从一开始就使用LinkedList开始每个元素,我们也可以&#34;保存&#34;我们正在使用Iterator处理的当前元素。在Iterator的帮助下,当在LinkedList中工作时,我们获得了remove()和insert()的O(1)效率。让它成为唯一的性能优势我知道LinkedList总是比ArrayList更好。

答案 4 :(得分:1)

答案1:ArrayList使用引擎盖下的数组。访问ArrayList对象的成员就像在提供的索引处访问数组一样简单,假设索引在支持数组的范围内。 LinkedList必须遍历其成员才能到达第n个元素。这是LinkedList的O(n),而ArrayList是O(1)。

答案 5 :(得分:1)

在LinkedList中,元素具有对其前后元素的引用。在ArrayList中,数据结构只是一个数组。

  1. LinkedList需要遍历N个元素以获取第N个元素。 ArrayList只需要返回后备数组的元素N。

  2. 需要为新大小重新分配后备数组,并且需要向上移动已删除元素以填充空白空间后复制数组或每个元素。 LinkedList只需要在删除之后将元素上的前一个引用设置为删除之前的元素和元素之后的元素的下一个引用,之后删除元素之后的元素。更长的解释,但做得更快。

  3. 与删除相同的原因。

答案 6 :(得分:1)

ArrayList

    如果我们的常用操作是检索操作,则
  • ArrayList是最佳选择。
  • 如果我们的操作是在中间插入和删除,则
  • ArrayList是最差的选择,因为内部执行了多次移位操作。
  • 在ArrayList中,元素将存储在连续的内存位置中,因此检索操作将变得容易。

链接列表:-

    如果我们的常用操作是在中间插入和删除,则
  • LinkedList是最佳选择。
  • LinkedList是最糟糕的选择,因为我们的频繁操作是检索操作。
  • 在LinkedList中,元素不会存储在连续的内存位置中,因此检索操作将很复杂。

现在来回答您的问题:-

1)ArrayList根据索引保存数据,并实现RandomAccess接口,该接口是标记接口,可以对ArrayList进行随机检索,但是LinkedList没有实现RandomAccess接口,这就是ArrayList比LinkedList更快的原因。

2)LinkedList的基础数据结构是双链表,因此在LinkedList中在中间插入和删除非常容易,因为不必像ArrayList一样为每个删除和插入操作移动每个元素(如果我们的操作是在中间插入和删除,则不建议这样做,因为内部执行了多次shift操作。)
Source

答案 7 :(得分:0)

ArrayList :ArrayList有一个类似数组的结构,它直接引用每个元素。因此,ArrayList中的rendom访问速度很快。

LinkedList :在LinkedList中获取第n个元素,你必须遍历整个列表,与ArrayList相比需要时间。每个元素都有一个链接到它以前的&amp;嵌套元素,因此删除速度很快。

答案 8 :(得分:0)

ArrayList: ArrayList类扩展了AbstractList并实现了List接口和RandomAccess(标记接口)。 ArrayList支持可根据需要增长的动态数组。 它为我们提供了第一次迭代元素。

LinkedList: LinkedList按索引位置排序,如ArrayList,除了元素彼此双向链接。这种链接为您提供了新的方法(除了从List接口获得的内容),用于从开头或结尾添加和删除,这使得它成为实现堆栈或队列的简单选择。请记住,LinkedList的迭代速度可能比ArrayList慢,但是当你需要快速插入和删除时,它是一个不错的选择。从Java 5开始,LinkedList类已得到增强,可以实现java。 util.Queue接口。因此,它现在支持常见的队列方法:peek(),poll()和offer()。

答案 9 :(得分:0)

我想补充一点有关性能差异的信息。

我们已经知道,由于ArrayList实现由Object[]支持,它支持随机访问和动态调整大小,并且LinkedList实现使用对头和尾的引用来导航它。它没有随机访问功能,但也支持动态调整大小。

第一件事是,使用ArrayList,您可以立即访问索引,而使用LinkedList,则可以遍历对象链。

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

但是,有趣的事情是,当您创建一个已经足够大以适合所有插入的ArrayList 时,它显然不会涉及任何数组复制操作。添加它比使用LinkedList更快,因为LinkedList必须处理其指针,而巨大的ArrayList只是在给定索引处设置值。

enter image description here

查看更多ArrayList and LinkedList differences

答案 10 :(得分:-1)

即使它们看起来相同(相同的实现接口列表 - 非线程安全),它们在添加/删除和搜索时间以及消耗内存方面的性能方面给出不同的结果(LinkedList消耗更多)。

如果使用性能为O(1)的高度插入/删除,则可以使用LinkedLists。 如果使用性能为O(1)

的直接访问操作,则可以使用ArrayLists

此代码可能会清除这些注释,您可以尝试了解效果结果。 (对不起锅炉板代码)

public class Test {

    private static Random rnd;


    static {
        rnd = new Random();
    }


    static List<String> testArrayList;
    static List<String> testLinkedList;
    public static final int COUNT_OBJ = 2000000;

    public static void main(String[] args) {
        testArrayList = new ArrayList<>();
        testLinkedList = new LinkedList<>();

        insertSomeDummyData(testLinkedList);
        insertSomeDummyData(testArrayList);

        checkInsertionPerformance(testLinkedList);  //O(1)
        checkInsertionPerformance(testArrayList);   //O(1) -> O(n)

        checkPerformanceForFinding(testArrayList);  // O(1)
        checkPerformanceForFinding(testLinkedList); // O(n)

    }


    public static void insertSomeDummyData(List<String> list) {
        for (int i = COUNT_OBJ; i-- > 0; ) {
            list.add(new String("" + i));
        }
    }

    public static void checkInsertionPerformance(List<String> list) {

        long startTime, finishedTime;
        startTime = System.currentTimeMillis();
        int rndIndex;
        for (int i = 200; i-- > 0; ) {
            rndIndex = rnd.nextInt(100000);
            list.add(rndIndex, "test");
        }
        finishedTime = System.currentTimeMillis();
        System.out.println(String.format("%s time passed at insertion:%d", list.getClass().getSimpleName(), (finishedTime - startTime)));
    }

    public static void checkPerformanceForFinding(List<String> list) {

        long startTime, finishedTime;
        startTime = System.currentTimeMillis();
        int rndIndex;
        for (int i = 200; i-- > 0; ) {
            rndIndex = rnd.nextInt(100000);
            list.get(rndIndex);
        }
        finishedTime = System.currentTimeMillis();
        System.out.println(String.format("%s time passed at searching:%d", list.getClass().getSimpleName(), (finishedTime - startTime)));

    }

}