关于java:ArrayList和LinkedList之间的性能差异

Performance differences between ArrayList and LinkedList

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

在Java中,人们说:

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

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

    1
    2
    3
    4
    5
    6
    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"
  • LinkedList比ArrayList更快插入。 插入意味着什么? 如果它意味着将一些元素移回然后将元素放在中间空白点,则ArrayList应该比LinkedList慢。 如果插入仅意味着添加(对象)操作,那么这怎么可能很慢?


  • ArrayList is faster than LinkedList if I randomly access its elements. I think random access means"give me the nth element". Why ArrayList is faster?

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

    LinkedList is faster than ArrayList for deletion. I understand this one. ArrayList's slower since the internal backing-up array needs to be reallocated.

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

    If it means move some elements back and then put the element in the middle empty spot, ArrayList should be slower.

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


    暂时忽略这个答案。其他答案,尤其是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,并计划大量插入和删除以及无随机访问,请运行一些基准测试以查看最快的内容。


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

    1
    2
    3
    4
    public ArrayList< T > {
        private Object[] array;
        private int size;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public LinkedList< T > {
        class Node< T > {
            T data;
            Node next;
            Node prev;
        }
        private Node< T > first;
        private Node< T > last;
        private int size;
    }

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

    ArrayList is faster than LinkedList if I randomly access its elements. I think random access means"give me the nth element". Why ArrayList is faster?

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

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

    LinkedList is faster than ArrayList for deletion. I understand this one. ArrayList's slower since the internal backing-up array needs to be reallocated.

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

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

    LinkedList is faster than ArrayList for deletion. I understand this one. ArrayList's slower since the internal backing-up array needs to be reallocated.

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

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

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

    更多信息:

    • http://en.wikipedia.org/wiki/Array_data_structure
    • http://en.wikipedia.org/wiki/Linked_list


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

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

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

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

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


    数组列表

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

    链表: -

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

    现在回答你的问题: -

    1)ArrayList根据索引保存数据,它实现了RandomAccess接口,这是一个标记接口,为ArrayList提供随机检索功能,但LinkedList不实现RandomAccess接口,这就是ArrayList比LinkedList更快的原因。

    2)LinkedList的底层数据结构是双链表,因此在LinkedList中插入和删除非常容易,因为它不必像每个删除和插入操作那样移动每个元素就像ArrayList(这是如果我们的操作是在中间插入和删除,因为内部执行了几个移位操作,所以不建议这样做。
    资源


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

  • LinkedList需要迭代N个元素以获得第N个元素。 ArrayList只需要返回后备数组的元素N.

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

  • 与此处删除的原因相同。


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


    我想补充一些关于性能差异的信息。

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

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

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

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

    enter image description here

    查看更多ArrayList和LinkedList差异。


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

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


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

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


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

    如果您使用性能为O(1)的高度插入/删除,则可以使用LinkedLists。
    如果使用性能为O(1)的直接访问操作,则可以使用ArrayLists

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    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)));

        }

    }