我一直是一个简单使用的人:
List<String> names = new ArrayList<>();
我将接口用作可移植性的类型名称,这样当我问诸如此类的问题时,便可以重新编写代码。
什么时候应该LinkedList
使用过ArrayList
,反之亦然?
我一直是一个简单使用的人:
List<String> names = new ArrayList<>();
我将接口用作可移植性的类型名称,这样当我问诸如此类的问题时,便可以重新编写代码。
什么时候应该LinkedList
使用过ArrayList
,反之亦然?
Answers:
摘要 ArrayList
用ArrayDeque
在最好许多更多使用情况比LinkedList
。如果不确定,请从开始ArrayList
。
LinkedList
并且ArrayList
是List接口的两种不同的实现。LinkedList
用双向链表实现它。ArrayList
用动态调整大小的数组实现它。
与标准的链表和数组操作一样,各种方法将具有不同的算法运行时。
get(int index)
是O(n)(平均有n / 4步),但在或时为O(1)(在这种情况下,您也可以使用和)。的主要好处之一index = 0
index = list.size() - 1
getFirst()
getLast()
LinkedList<E>
add(int index, E element)
是O(n)(平均n / 4步),但在或时为O(1)(在这种情况下,您也可以使用和/ )。的主要好处之一index = 0
index = list.size() - 1
addFirst()
addLast()
add()
LinkedList<E>
remove(int index)
是O(n)(平均有n / 4步),但在或时为O(1)(在这种情况下,您也可以使用和)。的主要好处之一index = 0
index = list.size() - 1
removeFirst()
removeLast()
LinkedList<E>
Iterator.remove()
是O(1)。的主要好处之一 LinkedList<E>
ListIterator.add(E element)
是O(1)。的主要好处之一 LinkedList<E>
注意:许多操作平均需要n / 4步,在最佳情况下(例如,索引= 0)需要恒定的步数,在最坏情况下(列表的中间)需要n / 2步
对于 ArrayList<E>
get(int index)
是O(1)。主要优点 ArrayList<E>
add(E element)
被分摊为O(1),但最差情况为O(n),因为必须调整数组大小并复制该数组add(int index, E element)
是O(n)(平均有n / 2步)remove(int index)
是O(n)(平均有n / 2步)Iterator.remove()
是O(n)(平均有n / 2步)ListIterator.add(E element)
是O(n)(平均有n / 2步)注意:许多操作平均需要n / 2步,在最佳情况下(列表结束)需要恒定的步数,在最坏情况下(列表开始)需要n步
LinkedList<E>
允许使用迭代器进行固定时间的插入或删除,但只能顺序访问元素。换句话说,您可以向前或向后浏览列表,但是在列表中查找位置所花费的时间与列表的大小成正比。Javadoc说:“索引到列表中的操作将从开头或结尾遍历列表,以较近者为准”,因此,这些方法的平均值为O(n)(n / 4步),尽管O(1)为index = 0
。
ArrayList<E>
另一方面,允许快速随机读取访问,因此您可以在固定时间内抓取任何元素。但是从末端开始的任何地方添加或删除,都需要将后面的所有元素移开,以形成开口或填补空白。同样,如果添加的元素多于基础数组的容量,则会分配一个新数组(大小为1.5倍),并将旧数组复制到新数组中,因此在最坏的情况下将a添加ArrayList
为O(n)情况,但平均保持不变。
因此,根据您打算执行的操作,您应该相应地选择实现。遍历任何一种List实际上都是很便宜的。(从ArrayList
技术上讲,迭代速度更快,但除非您确实对性能敏感,否则您不必为此担心-它们都是常量。)
LinkedList
当您重复使用现有迭代器来插入和删除元素时,使用generic 的主要好处。然后可以通过仅在本地更改列表在O(1)中完成这些操作。在阵列列表中,阵列的其余部分需要移动(即复制)。另一方面,在最差的情况下LinkedList
以O(n)(n / 2步)的链接寻找均值,而在ArrayList
所需位置可以数学计算并在O(1)中访问。
使用的另一个好处LinkedList
出现当您从列表的头部添加或删除,因为这些操作是O(1) ,而他们是为O(n)进行ArrayList
。请注意,这ArrayDeque
可能是LinkedList
添加和删除头部的不错选择,但不是List
。
另外,如果列表很大,请记住,内存使用情况也有所不同。a的每个元素LinkedList
都有更多开销,因为还存储了指向下一个和上一个元素的指针。ArrayLists
没有这个开销。但是,ArrayLists
无论是否实际添加了元素,占用的内存都应为该容量分配的内存。
的默认初始容量ArrayList
很小(Java 1.4-1.8中为10)。但是由于底层实现是一个数组,因此如果添加很多元素,则必须调整数组的大小。为了避免在确定要添加很多元素时调整大小的高成本,请ArrayList
以较高的初始容量构造。
O(n/2)
或的东西O(n/4)
。大号O表示您如何用更大的n 缩放操作。需要逐步执行的操作与需要逐步执行的操作完全一样地按比例缩放,这就是删除常量求幂或因数的原因。而且都是正义的。并且具有不同的常数因子,因此将a中的a 与另一个中的a 进行比较就没有意义,两者都只表示线性缩放操作。n/2
n
O(n/2)
O(n/4)
O(n)
LinkedList
ArrayList
O(n/2)
O(n/4)
到目前为止,除了普遍认为a LinkedList
比a “多得多”之外,似乎没有人解决过这些列表的内存占用问题,ArrayList
因此我进行了一些数字运算来证明这两个列表占用了N个空引用的确切数量。
由于参考在它们的相对系统上是32位还是64位(即使为null),因此我包括了32和64位LinkedLists
and的4组数据ArrayLists
。
注意:各ArrayList
行显示的大小适用于修剪后的列表 -实际上,中的后备阵列的容量ArrayList
通常大于其当前元素数。
注意2 :( 感谢BeeOnRope)由于CompressedOops现在是JDK6或更高版本的默认值,因此下面的64位计算机的值将基本上与32位计算机匹配,除非您明确地将其关闭。
结果清楚地表明 LinkedList
远远超过ArrayList
,尤其是元素数量很高时。如果内存是一个因素,请避免使用LinkedLists
。
我遵循的公式如下,如果我做错了什么,请告诉我,我将对其进行修复。对于32或64位系统,“ b”为4或8,而“ n”为元素数。请注意,使用mod的原因是因为java中的所有对象将占用8字节空间的倍数,而不管其是否全部使用。
数组列表:
ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)
链表:
LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)
int
,即4或8个字节的数据。在链表中,开销实际上有4个“单词”。因此,您的图形给人的印象是,链表使用“五次”存储阵列列表。错了 每个对象的开销为16或32字节,作为累加调整,而不是缩放因子。
CompressedOops
是默认的,现在在所有近期的JDK(7,8和几年的6更新),所以64位不会让一个差异ArrayList
或LinkedList
大小,除非您已明确关闭压缩为哎呀一些原因。
ArrayList
未指定初始容量的情况下填充时,其使用的内存仍然比少得多LinkedList
。
ArrayList
是你想要的。 LinkedList
几乎总是(性能)错误。
为什么LinkedList
烂:
ArrayList
。ArrayList
,反正它也可能会明显变慢。LinkedList
在源代码中看到它很令人讨厌,因为这可能是错误的选择。作为从事大型SOA Web服务的运营性能工程研究大约十年的人,我希望LinkedList的行为优于ArrayList。尽管LinkedList的稳态吞吐量较差,因此可能导致购买更多硬件-ArrayList的行为在压力下可能导致群集中的应用程序几乎同步地扩展其阵列,并且对于较大的阵列大小,可能导致缺乏响应能力在应用程序中,并且在压力下中断,这是灾难性的行为。
同样,您可以从默认吞吐量的使用期限垃圾收集器中获得更好的应用程序吞吐量,但是一旦获得具有10GB堆的Java应用程序,您就可以在Full GC期间将应用程序锁定25秒,这会导致SOA应用程序超时和失败并在您的SLA频繁发生时将其删除。即使CMS收集器占用更多资源且未达到相同的原始吞吐量,它也是一个更好的选择,因为它具有更可预测的延迟和更小的延迟。
如果性能仅是吞吐量,而您可以忽略延迟,则ArrayList仅是性能的更好选择。根据我的工作经验,我不能忽略最坏情况下的延迟。
LinkedList
总是分配的内存是普通引用数组的ArrayList
五倍,因此即使不回收内存,暂时需要2.5倍的内存仍然消耗少得多的内存。由于大型数组分配绕过了Eden空间,因此它们不会对GC行为产生任何影响,除非确实没有足够的内存,在这种情况下,LinkedList
爆炸的时间要早得多……
LinkedList
只需要一小块可用内存即可分配给下一个元素。ArrayList
将需要一个大而连续的自由空间来分配调整大小的数组。如果堆碎片化,则GC可能最终会重新排序整个堆,以释放适当的单个内存块。
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
ArrayLists对于一次写入多次读取或追加程序很有用,但不利于从前端或中间进行添加/删除。
O(1)
。它必须遍历列表的一半才能找到插入点。
LinkedList
是 O(1)
,如果你有一个迭代器插入位置,即ListIterator.add
是所谓O(1)
的LinkedList
。
是的,我知道,这是一个古老的问题,但是我将投入两分钱:
从性能角度来看,LinkedList 几乎总是错误的选择。有一些非常特定的算法需要使用LinkedList,但是这种算法非常非常少见,该算法通常具体取决于LinkedList在列表中导航后相对快速地在列表中间插入和删除元素的能力。与ListIterator。
在一种常见的使用情况下,LinkedList的性能优于ArrayList:队列的性能。但是,如果您的目标是性能,那么您还应该考虑使用ArrayBlockingQueue(如果可以提前确定队列大小的上限,并且可以负担所有内存的分配),而不要使用LinkedList,或者使用此CircularArrayList实现。(是的,它是从2001年开始的,因此您需要对其进行泛化,但是我得到的性能比与最近的JVM中本文中引用的性能比相当)
ArrayDeque
。docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html
ArrayDeque
比LinkedList
除非所有操作都在同一末端要慢。用作堆栈是可以的,但是并不能很好地排队。
ArrayDeque
可能比Stack
用作堆栈时要快,并且比LinkedList
用作队列时要快。
ArrayDeque
文档。
这是一个效率问题。LinkedList
用于添加和删除元素的速度很快,但是访问特定元素的速度很慢。ArrayList
用于访问特定元素的速度很快,但添加到任一端的速度可能很慢,尤其是在中间删除时速度很慢。
Array vs ArrayList vs LinkedList vs Vector以及Linked List的深度更深 。
正确或不正确:请在本地执行测试,然后自己决定!
中的“编辑/删除”速度LinkedList
比快ArrayList
。
ArrayList
Array
在大容量应用程序中,需要大小加倍的,由,作为后缀,则更糟。
下面是每个操作的单元测试结果,计时以纳秒为单位。
Operation ArrayList LinkedList
AddAll (Insert) 101,16719 2623,29291
Add (Insert-Sequentially) 152,46840 966,62216
Add (insert-randomly) 36527 29193
remove (Delete) 20,56,9095 20,45,4904
contains (Search) 186,15,704 189,64,981
这是代码:
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class ArrayListVsLinkedList {
private static final int MAX = 500000;
String[] strings = maxArray();
////////////// ADD ALL ////////////////////////////////////////
@Test
public void arrayListAddAll() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
arrayList.addAll(stringList);
watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
}
@Test
public void linkedListAddAll() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
watch.start();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
watch.totalTime("Linked List addAll() = "); //2623,29291 Nanoseconds
}
//Note: ArrayList is 26 time faster here than LinkedList for addAll()
///////////////// INSERT /////////////////////////////////////////////
@Test
public void arrayListAdd() {
Watch watch = new Watch();
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
for (String string : strings)
arrayList.add(string);
watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
}
@Test
public void linkedListAdd() {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
watch.start();
for (String string : strings)
linkedList.add(string);
watch.totalTime("Linked List add() = "); //966,62216 Nanoseconds
}
//Note: ArrayList is 9 times faster than LinkedList for add sequentially
/////////////////// INSERT IN BETWEEN ///////////////////////////////////////
@Test
public void arrayListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
arrayList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
arrayList.add(insertString0);
arrayList.add(insertString1);
arrayList.add(insertString2);
arrayList.add(insertString3);
watch.totalTime("Array List add() = ");//36527
}
@Test
public void linkedListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
linkedList.add(insertString0);
linkedList.add(insertString1);
linkedList.add(insertString2);
linkedList.add(insertString3);
watch.totalTime("Linked List add = ");//29193
}
//Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.
////////////////// DELETE //////////////////////////////////////////////////////
@Test
public void arrayListRemove() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.remove(searchString0);
arrayList.remove(searchString1);
watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
}
@Test
public void linkedListRemove() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.remove(searchString0);
linkedList.remove(searchString1);
watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
}
//Note: LinkedList is 10 millisecond faster than ArrayList while removing item.
///////////////////// SEARCH ///////////////////////////////////////////
@Test
public void arrayListSearch() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.contains(searchString0);
arrayList.contains(searchString1);
watch.totalTime("Array List addAll() time = ");//186,15,704
}
@Test
public void linkedListSearch() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.contains(searchString0);
linkedList.contains(searchString1);
watch.totalTime("Linked List addAll() time = ");//189,64,981
}
//Note: Linked List is 500 Milliseconds faster than ArrayList
class Watch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
private void stop() {
endTime = System.nanoTime();
}
public void totalTime(String s) {
stop();
System.out.println(s + (endTime - startTime));
}
}
private String[] maxArray() {
String[] strings = new String[MAX];
Boolean result = Boolean.TRUE;
for (int i = 0; i < MAX; i++) {
strings[i] = getString(result, i);
result = !result;
}
return strings;
}
private String getString(Boolean result, int i) {
return String.valueOf(result) + i + String.valueOf(!result);
}
}
LinkedList
具有更多的内存开销,因为每个元素都有一个具有五个字段的节点对象。在许多系统上,这会产生20字节的开销。每个元素的平均内存开销为ArrayList
一个半字,即6个字节,最坏情况下为8个字节。
removeIf(element -> condition)
适合的位置ArrayList
,与通过迭代器循环和删除相比,适合使用的地方要快得多,因为不需要为每个元素移动整个余数。它的性能好坏LinkedList
取决于特定情况,就像LinkedList
理论上的O(1)一样,但是仅删除单个节点就需要几次内存访问,这很容易超过ArrayList
删除大量元素时所需的数量。 。
ArrayList
本质上是一个数组。LinkedList
被实现为双链表。
这get
很清楚。O(1)为ArrayList
,因为ArrayList
允许使用索引进行随机访问。的O(n)LinkedList
,因为它需要首先找到索引。注意:add
和的版本不同remove
。
LinkedList
添加和删除速度更快,但获取速度较慢。简而言之,在以下情况下LinkedList
应首选:
=== ArrayList ===
=== LinkedList ===
加(E e)
add(int index,E元素)
这是来自programcreek.com的图(add
并且remove
是第一种类型,即在列表的末尾添加一个元素,然后在列表中的指定位置删除该元素。):
Joshua Bloch,LinkedList的作者:
有人实际使用LinkedList吗?我写了它,但从未使用过。
链接: https //twitter.com/joshbloch/status/583813919019573248
对于该答案没有其他答案那么有用,我感到抱歉,但我认为这将是最有趣且最能说明问题的。
ArrayList
可以随机访问,而LinkedList
扩展和删除元素确实很便宜。在大多数情况下,ArrayList
都可以。
除非您创建了大列表并解决了瓶颈问题,否则您将永远不必担心差异。
由于采用现代计算机体系结构,TL; DRArrayList
在几乎所有可能的用例中都将显着提高效率-因此LinkedList
应避免使用,除非某些非常独特和极端的情况。
理论上,LinkedList的O(1)为 add(E element)
同样,在列表中间添加元素应该非常有效。
实际情况非常不同,因为LinkedList是一种缓存敌对数据结构。从性能POV来看-在极少数情况下,LinkedList
比对Cache友好的性能可能更好 ArrayList
。
这是基准测试在随机位置插入元素的结果。如您所见-数组列表效率更高,尽管从理论上讲,列表中间的每个插入都需要“移动” 数组的后n个元素(值越低越好):
在下一代硬件(更大,更高效的缓存)上工作-结果更加确定:
LinkedList需要更多的时间来完成相同的工作。源 代码
这有两个主要原因:
主要是 -的节点LinkedList
随机分散在整个内存中。RAM(“随机访问内存”)并不是真正随机的,需要获取内存块以进行缓存。此操作需要时间,并且在频繁发生此类提取时-需要一直替换高速缓存中的内存页面->高速缓存未命中->高速缓存效率不高。
ArrayList
元素存储在连续的内存中-这正是现代CPU架构正在优化的内容。
LinkedList
要求保留后退/前进指针的辅助服务器,这意味着与相比,每个存储值的内存消耗是3倍ArrayList
。
DynamicIntArray btw是一个自定义ArrayList实现,包含Int
(原始类型)而不是Objects-因此,所有数据实际上都是相邻存储的-因此效率更高。
需要记住的一个关键因素是,获取存储块的成本比访问单个存储单元的成本更为重要。这就是为什么读取器1MB顺序内存比从不同内存块中读取此数据量快达x400倍的原因:
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
资料来源:每个程序员都应该知道的延迟数
为了使观点更清楚,请检查将元素添加到列表开头的基准。从理论上讲,这是一个用例,应在理论上LinkedList
真正发光,并ArrayList
应提供较差甚至更差的结果:
注意:这是C ++ Std库的基准,但是我以前的经验表明C ++和Java的结果非常相似。源代码
复制连续的内存是现代CPU所优化的一项操作-改变了理论,实际上又使ArrayList
/ Vector
提高了效率
鸣谢:此处发布的所有基准都是由KjellHedström创建的。可以在他的博客上找到更多数据
如果您的代码包含add(0)
和remove(0)
,请使用LinkedList
和,这是更漂亮的addFirst()
和removeFirst()
方法。否则,使用ArrayList
。
当然,Guava的ImmutableList是您最好的朋友。
这是ArrayList
和LinkedList
和中的Big-O表示法CopyOnWrite-ArrayList
:
数组列表
get O(1)
add O(1)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
链表
get O(n)
add O(1)
contains O(n)
next O(1)
remove O(1)
iterator.remove O(1)
CopyOnWrite-ArrayList
get O(1)
add O(n)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
基于这些,您必须决定选择什么。:)
LinkedList.add()
,尽管这里的大多数答案都是这样。
让我们比较以下参数的LinkedList和ArrayList:
ArrayList是list接口的可调整大小的数组实现,而
LinkedList是列表接口的双链列表实现。
ArrayList get(int index)操作以恒定时间运行,即O(1)而
LinkedList get(int index)操作的运行时间为O(n)。
背后的原因的ArrayList比链表更快是ArrayList的使用索引为基础的系统为它的部分,因为它在内部使用一个阵列,在另一方面,
LinkedList不对其元素提供基于索引的访问,因为它从开始或结束(以较近者为准)进行迭代以检索指定元素索引处的节点。
与ArrayList相比,LinkedList中的插入通常较快。在LinkedList中,添加或插入是O(1)操作。
在ArrayList中,如果数组是完整的(即最坏的情况),则需要调整数组大小并将元素复制到新数组中,这会产生额外的开销,这会使ArrayList中的添加操作运行时间为O(n),否则为O(1) 。
LinkedList中的删除操作通常与ArrayList相同,即O(n)。
在LinkedList中,有两个重载的remove方法。一个是remove(),不带任何参数,该参数删除列表的开头并以恒定时间O(1)运行。LinkedList中另一个重载的remove方法是remove(int)或remove(Object),它们删除作为参数传递的Object或int。此方法遍历LinkedList,直到找到对象并将其与原始列表取消链接。因此,此方法的运行时间为O(n)。
在ArrayList中, remove(int)方法涉及将元素从旧数组复制到新的更新数组,因此其运行时间为O(n)。
LinkedList可以使用下降的Iterator()反向反向迭代,而
ArrayList中没有下降的Iterator(),因此我们需要编写自己的代码以反向遍历ArrayList。
如果构造函数未过载,则ArrayList将创建一个初始容量为10的空列表,而
LinkedList 仅构造没有任何初始容量的空列表。
LinkedList中的内存开销与ArrayList相比更多,因为中的一个节点需要维护下一个和上一个节点的地址。而
在ArrayList中, 每个索引仅保存实际的对象(数据)。
我通常会根据要在该特定List上执行的操作的时间复杂性来使用另一个。
|---------------------|---------------------|--------------------|------------|
| Operation | ArrayList | LinkedList | Winner |
|---------------------|---------------------|--------------------|------------|
| get(index) | O(1) | O(n) | ArrayList |
| | | n/4 steps in avg | |
|---------------------|---------------------|--------------------|------------|
| add(E) | O(1) | O(1) | LinkedList |
| |---------------------|--------------------| |
| | O(n) in worst case | | |
|---------------------|---------------------|--------------------|------------|
| add(index, E) | O(n) | O(n) | LinkedList |
| | n/2 steps | n/4 steps | |
| |---------------------|--------------------| |
| | | O(1) if index = 0 | |
|---------------------|---------------------|--------------------|------------|
| remove(index, E) | O(n) | O(n) | LinkedList |
| |---------------------|--------------------| |
| | n/2 steps | n/4 steps | |
|---------------------|---------------------|--------------------|------------|
| Iterator.remove() | O(n) | O(1) | LinkedList |
| ListIterator.add() | | | |
|---------------------|---------------------|--------------------|------------|
|--------------------------------------|-----------------------------------|
| ArrayList | LinkedList |
|--------------------------------------|-----------------------------------|
| Allows fast read access | Retrieving element takes O(n) |
|--------------------------------------|-----------------------------------|
| Adding an element require shifting | o(1) [but traversing takes time] |
| all the later elements | |
|--------------------------------------|-----------------------------------|
| To add more elements than capacity |
| new array need to be allocated |
|--------------------------------------|
数组列表本质上是一个带有添加项目等方法的数组(您应该改用通用列表)。它是可通过索引器访问的项目的集合(例如[0])。它意味着从一项到另一项的进展。
链接列表指定从一项到下一项的进度(项目a->项目b)。您可以使用数组列表获得相同的效果,但是链接列表绝对说明了应该遵循上一个项目的项目。
链接列表的一个重要功能(我没有在其他答案中读过)是两个列表的串联。对于数组,这是O(n)(加上某些重新分配的开销),并且只有链表,这仅是O(1)或O(2);-)
重要提示:对于Java,LinkedList
这不是事实!请参阅Java中是否有用于链接列表的快速concat方法?
next
从一个列表指向第二个列表中的第一个节点。唯一的方法是使用addAll()
顺序添加元素,尽管比遍历并调用add()
每个元素更好。要在O(1)中快速完成此操作,您将需要一个合成类(例如org.apache.commons.collections.collection.CompositeCollection),但是它适用于任何类型的List / Collection。
ArrayList和LinkedList具有各自的优缺点。
与使用指向下一个节点的指针的LinkedList相比,ArrayList使用连续的内存地址。因此,当您要查找ArrayList中的元素时,比使用LinkedList执行n次迭代要快。
另一方面,在LinkedList中插入和删除要容易得多,因为您只需要更改指针,而ArrayList意味着对任何插入或删除都使用了shift操作。
如果您的应用程序中经常进行检索操作,请使用ArrayList。如果您经常插入和删除,请使用LinkedList。
ArrayList
和LinkedList
这两个工具List interface
和他们的方法和结果几乎是相同的。但是,根据需要,它们之间几乎没有什么区别,一个可以使另一个更好。
1)与Search:
ArrayList
搜索操作相比,LinkedList
搜索操作相当快。get(int index)
在ArrayList
给出性能的O(1)
同时,给出的LinkedList
性能是O(n)
。
Reason:
ArrayList
由于隐式使用数组数据结构,因此可以为其元素维护基于索引的系统,这使得在列表中搜索元素的速度更快。另一方面,LinkedList
实现双向链表,该链表需要遍历所有元素以搜索元素。
2)Deletion:
LinkedList
删除操作可提供O(1)
性能,而ArrayList
可变性能:O(n)
在最坏的情况下(删除第一个元素时)和O(1)
在最佳情况下(删除最后一个元素时)。
结论:与ArrayList相比,LinkedList元素的删除速度更快。
原因:LinkedList的每个元素维护两个指针(地址),这些指针指向列表中的两个相邻元素。因此,删除仅需要更改将要删除的节点的两个相邻节点(元素)中的指针位置。而在ArrayList中,所有元素都需要移动以填充由删除的元素创建的空间。
3)Inserts Performance:
LinkedList
add方法给出O(1)
性能,而在最坏情况下ArrayList
给出O(n)
。原因与删除原因相同。
4)Memory Overhead:
ArrayList
维护索引和元素数据,同时LinkedList
维护元素数据和两个邻居节点指针
因此,LinkedList中的内存消耗相对较高。
iterator
和listIterator
返回fail-fast
(如果list在创建迭代器之后的任何时间进行结构修改,除非通过iterator’s
自己的remove或add方法,否则迭代器将为throw
a ConcurrentModificationException
)。(O(1))
,LinkedList
与相比,插入和删除操作具有良好的性能ArrayList(O(n))
。
因此,如果需要在应用程序中频繁添加和删除,则LinkedList是最佳选择。
get method
)操作快Arraylist (O(1))
但不快LinkedList (O(n))
因此,如果添加和删除操作更少而搜索操作需求更多,则ArrayList将是您最好的选择。
1)基础数据结构
ArrayList和LinkedList之间的第一个区别在于,ArrayList由Array支持,而LinkedList由LinkedList支持。这将导致性能上的进一步差异。
2)LinkedList实现双端队列
ArrayList和LinkedList之间的另一个区别是,除了List接口之外,LinkedList还实现了Deque接口,该接口为add()和poll()以及其他几个Deque函数提供了先进先出操作。3)在ArrayList中添加元素如果不触发Array的大小调整,则在ArrayList中添加元素是O(1)操作,在这种情况下,它变为O(log(n)),另一方面,在LinkedList是O(1)操作,因为它不需要任何导航。
4)从某个位置移除元素
为了从特定索引中删除元素(例如,通过调用remove(index)),ArrayList执行复制操作,使其接近O(n),而LinkedList需要遍历该点,这也使其变为O(n / 2) ,因为它可以根据接近度从任一方向来回移动。
5)遍历ArrayList或LinkedList
迭代是LinkedList和ArrayList的O(n)操作,其中n是元素的数量。
6)从位置检索元素
get(index)操作在ArrayList中为O(1),而在LinkedList中为O(n / 2),因为它需要遍历直到该条目。但是,在大O表示法中,O(n / 2)只是O(n),因为我们忽略了那里的常数。
7)记忆
LinkedList使用一个包装对象Entry,这是一个静态嵌套类,用于存储数据以及上一个和下两个节点,而ArrayList仅将数据存储在Array中。
因此,对于ArrayList而言,内存需求似乎比LinkedList少,除了Array在将内容从一个Array复制到另一Array时执行调整大小操作的情况。
如果Array足够大,则此时可能会占用大量内存并触发垃圾回收,这会减慢响应时间。
从ArrayList与LinkedList之间的所有上述差异来看,在几乎所有情况下,ArrayList都是比LinkedList更好的选择,除非您经常执行add()操作而不是remove()或get()。
与ArrayList相比,修改链表更容易,尤其是在开始或结束添加或删除元素时,因为链表内部保留了这些位置的引用,并且可以在O(1)时间内访问它们。
换句话说,您无需遍历链表即可到达要添加元素的位置,在这种情况下,加法变为O(n)操作。例如,在链接列表的中间插入或删除元素。
我认为,将ArrayList而不是LinkedList用作Java中的大多数实际用途。
我在这里看到的一项测试仅执行一次测试。但是我注意到,您需要多次运行这些测试,最终它们的时间会收敛。基本上,JVM需要预热。对于我的特定用例,我需要将项目添加/删除到增加到约500个项目的列表中。在我的测试中LinkedList
,速度更快,LinkedList
大约达到50,000 NS ArrayList
,大约达到90,000 NS ...付出或接受。请参见下面的代码。
public static void main(String[] args) {
List<Long> times = new ArrayList<>();
for (int i = 0; i < 100; i++) {
times.add(doIt());
}
System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}
static long doIt() {
long start = System.nanoTime();
List<Object> list = new LinkedList<>();
//uncomment line below to test with ArrayList
//list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(i);
}
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
long end = System.nanoTime();
long diff = end - start;
//uncomment to see the JVM warmup and get faster for the first few iterations
//System.out.println(diff)
return diff;
}
对于ArrayLists和LinkedLists,remove()和insert()的运行时效率均为O(n)。但是,线性处理时间背后的原因来自两个截然不同的原因:
在ArrayList中,您到达O(1)中的元素,但是实际上删除或插入某些内容会使它成为O(n),因为以下所有元素都需要更改。
在LinkedList中,实际上需要O(n)才能到达所需的元素,因为我们必须从头开始,直到达到所需的索引。实际上,删除或插入是常量,因为我们只需为remove()更改1个引用,为insert()更改2个引用。
两者中哪一个插入和删除速度更快,取决于发生的位置。如果我们更接近开始,LinkedList将会更快,因为我们必须经历相对较少的元素。如果我们接近末尾,则ArrayList会更快,因为我们可以在恒定时间内到达那里,而只需要更改跟随它的几个剩余元素即可。当恰好在中间完成LinkedList时,它会更快,因为通过n个元素比移动n个值要快。
奖励:虽然没有办法使这两个方法成为ArrayList的O(1),但实际上在LinkedLists中有一种方法可以做到这一点。假设我们要遍历整个List移除和插入元素。通常,您将使用LinkedList从每个元素的最开始开始,我们也可以“保存”正在使用Iterator的当前元素。借助Iterator,在LinkedList中工作时,remove()和insert()的效率为O(1)。使它成为唯一的性能优势,我知道LinkedList总是比ArrayList更好。
ArrayList扩展AbstractList并实现List接口。ArrayList是动态数组。
可以说,它的创建基本上是为了克服数组的缺点
LinkedList类扩展了AbstractSequentialList并实现了List,Deque和Queue接口。
性能
arraylist.get()
为O(1),而linkedlist.get()
O(n)
arraylist.add()
为O(1)和linkedlist.add()
0(1)
arraylist.contains()
为O(n)和linkedlist.contains()
O(n)
arraylist.next()
为O(1)和linkedlist.next()
O(1)
arraylist.remove()
为O(n)而linkedlist.remove()
O(1)
在arraylist中iterator.remove()
是O(n)
而链表中iterator.remove()
是O(1)