Java 为什么在LinkedList末尾添加元素比ArrayList需要更长的时间?

Java 为什么在LinkedList末尾添加元素比ArrayList需要更长的时间?,java,arraylist,data-structures,collections,linked-list,Java,Arraylist,Data Structures,Collections,Linked List,我了解到在LinkedList的末尾添加元素是O(1),因为我们只是将对象添加到列表的最后一个节点,ArrayList也是O(1),但最坏的情况是O(n),因为数组中没有空间时必须调整大小。我试着用我学到的结果来测试它,但是ArrayList比LinkedList添加1000万个元素的速度要快得多 多次尝试后,ArrayList大约花费了570毫秒,而LinkedList大约花费了1900毫秒 public static void listPractice(List<String>

我了解到在LinkedList的末尾添加元素是O(1),因为我们只是将对象添加到列表的最后一个节点,ArrayList也是O(1),但最坏的情况是O(n),因为数组中没有空间时必须调整大小。我试着用我学到的结果来测试它,但是ArrayList比LinkedList添加1000万个元素的速度要快得多

多次尝试后,ArrayList大约花费了570毫秒,而LinkedList大约花费了1900毫秒

public static void listPractice(List<String> test) {
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10_000_000; i++) {
        test.add(String.valueOf(i));
    }
    System.out.println(System.currentTimeMillis() - startTime);

}
公共静态无效列表实践(列表测试){
long startTime=System.currentTimeMillis();
对于(int i=0;i<10_000;i++){
test.add(String.valueOf(i));
}
System.out.println(System.currentTimeMillis()-startTime);
}

看起来很奇怪。我一直在阅读其他讨论,我看到很多程序员说LinkedList,简而言之,有点差劲。这是证据,还是我遗漏了什么?

向数组中添加元素(不需要调整大小)需要将引用复制到空单元格中,并增加数组中对象元素的计数

向链表添加元素需要分配一个新节点,将前一个最后一个条目的“下一个”链接指向新的最后一个条目,将新条目的“下一个”置零,将头部的“最后一个”链接指向新条目,并将新条目的前一个链接指向前一个最后一个条目。然后将对象引用复制到新节点中,并增加列表中的元素计数

简言之,链表需要分配,比数组版本多写入4次

但是,如果您认为链表很糟糕,请尝试在重复从包含1000万条目的数组中删除前元素的情况下使用数组

有一些技术可以避免在数组中洗牌所有条目,但是除非它是一个圆形数组,否则它可能会洗牌。我所看到的ArrayList源代码会进行无序移动,因此删除10000000个条目中的第一个条目会将剩余的9999999个条目向下移动。删除下一个条目将不得不向下移动剩余的999998个条目

更不用说1000万条目的阵列需要能够获得连续的40或80 MB内存


教训是,不同的数据结构有不同的最佳点。

向数组中添加元素(不需要调整大小)需要将引用复制到空单元格中,并增加数组中对象元素的计数

向链表添加元素需要分配一个新节点,将前一个最后一个条目的“下一个”链接指向新的最后一个条目,将新条目的“下一个”置零,将头部的“最后一个”链接指向新条目,并将新条目的前一个链接指向前一个最后一个条目。然后将对象引用复制到新节点中,并增加列表中的元素计数

简言之,链表需要分配,比数组版本多写入4次

但是,如果您认为链表很糟糕,请尝试在重复从包含1000万条目的数组中删除前元素的情况下使用数组

有一些技术可以避免在数组中洗牌所有条目,但是除非它是一个圆形数组,否则它可能会洗牌。我所看到的ArrayList源代码会进行无序移动,因此删除10000000个条目中的第一个条目会将剩余的9999999个条目向下移动。删除下一个条目将不得不向下移动剩余的999998个条目

更不用说1000万条目的阵列需要能够获得连续的40或80 MB内存


教训是,不同的数据结构有不同的最佳点。

简单的回答是数组是随机访问的数据结构。链表不是。因此,对于后一种情况,除非它是Deque,否则您需要在列表上迭代以找到结尾。大多数链表实现在列表头中保留一个尾部指针,以便在一个操作中找到最后一个元素。LinkedList的Java实现也是如此,至少在我看到的源代码中是如此。@IvanSyrups不一定如此。大O谈论某事的表现。但是你不能孤立地谈论链表的性能。这取决于你在做什么。在HashMap实现中,各个bucket是链表,您通常只需在前面添加新条目。至少它曾经是这样的(有一段时间没有检查过)。但是当时没有尾部指针。@WJS是一个(javadoc这样说),列表同时引用了列表的头和尾,所以附加到列表中的总是O(1),因为它永远不必“迭代列表以找到结尾”。--问题是关于
LinkedList
实现的,而不是关于
HashMap
中的其他一些链表实现,因此引用完全没有抓住要点,与问题无关。简短的回答是数组是随机访问数据结构。链表不是。因此,对于后一种情况,除非它是Deque,否则您需要在列表上迭代以找到结尾。大多数链表实现在列表头中保留一个尾部指针,以便在一个操作中找到最后一个元素。LinkedList的Java实现也是如此,至少在我看到的源代码中是如此。@IvanSyrups不一定如此。大O谈论某事的表现。但是你不能孤立地谈论链表的性能。这取决于你在做什么。在HashMap实现中,各个bucket是链表,您通常只需在前面添加新条目。至少它曾经是这样的(h