何时通过ArrayList使用LinkedList?
我一直只使用一个:
List<String> names = new ArrayList<>();
我使用接口作为可移植性的类型名称,以便当我提出这些问题时,我可以重写我的代码。
什么时候应该将LinkedList
用于ArrayList
,反之亦然?
总结 ArrayList
和ArrayDeque
在比LinkedList
更多的用例中是可取的。 不确定 - 只需从ArrayList
开始。
LinkedList
和ArrayList
是List接口的两种不同实现。 LinkedList
用一个双向LinkedList
实现它。 ArrayList
通过动态调整大小的数组来实现它。
与标准链表和数组操作一样,各种方法将具有不同的算法运行时。
对于LinkedList<E>
get(int index)
是O(n)(平均n / 4步) add(E element)
是O(1) add(int index, E element)
是O(n)(平均n / 4步),但O(1)当index = 0
<--- LinkedList<E>
主要优点LinkedList<E>
remove(int index)
是O(n)(平均n / 4步) Iterator.remove()
是O(1)。 <--- LinkedList<E>
主要优点 ListIterator.add(E element)
是O(1)这是LinkedList<E>
一个主要优点 注意:许多操作平均需要n / 4步,最佳情况下步数不变(例如index = 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倍大小),并将旧数组复制到新数组中,因此添加到ArrayList
中的O(n)是最坏的情况,但平均不变。
所以根据你打算做的操作,你应该选择相应的实现。 迭代任何一种List几乎同样便宜。 (对ArrayList
迭代在技术上更快,但除非你对性能敏感,否则不应该担心 - 它们都是常量。)
当你重用现有的迭代器来插入和删除元素时,使用LinkedList
的主要好处就产生了。 这些操作可以在O(1)中完成,只需在本地更改列表。 在数组列表中,数组的其余部分需要移动(即复制)。 另一方面,在LinkedList
寻找意味着在最坏情况下遵循O(n)(n / 2步)中的链接,而在ArrayList
,可以通过数学计算并在O(1)中访问所需的位置。
使用LinkedList
另一个好处是当你从列表的头部添加或移除时产生的,因为这些操作是O(1),而它们是ArrayList
O(n)。 请注意, ArrayDeque
可能是LinkedList
一个很好的替代方案,用于从头部添加和删除,但它不是List
。
另外,如果您有大型列表,请记住内存使用情况也不同。 LinkedList
每个元素都有更多的开销,因为指向下一个元素和前一个元素的指针也被存储。 ArrayLists
没有这种开销。 但是,无论元素是否实际添加, ArrayLists
占用的容量都与分配的容量相同。
ArrayList
的默认初始容量非常小(10个来自Java 1.4 - 1.8)。 但是由于底层实现是一个数组,因此如果添加了很多元素,则必须调整数组的大小。 为了避免在知道要添加大量元素时调整大小的高成本,请使用较高的初始容量构建ArrayList
。
到目前为止,除了普遍认为LinkedList
比ArrayList
“多得多”之外,似乎没有人会解决这些列表中的每个列表的内存占用情况,所以我做了一些数字处理以示范两个列表占用多少N空引用。
由于引用在其相关系统上是32位或64位(即使为空),我已经为32位和64位LinkedLists
和ArrayLists
包含了4组数据。
注意: ArrayList
行显示的大小是用于修剪列表的 - 实际上, ArrayList
支持数组的容量通常大于其当前元素数。
注2:(感谢BeeOnRope)由于CompressedOops现在默认从JDK6开始,所以64位机器的值将基本上与32位对应的值匹配,除非您明确关闭它。
结果清楚地表明, LinkedList
比ArrayList
多得多,特别是在元素数量非常高的情况下。 如果记忆是一个因素,请避开LinkedLists
。
我使用的公式如下,让我知道如果我做错了什么,我会修复它。 对于32或64位系统,'b'是4或8,'n'是元素的数量。 注意mods的原因是因为java中的所有对象都占用8个字节的倍数,而不管它是否全部使用。
ArrayList
:
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
:
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)
ArrayList
是你想要的。 LinkedList
几乎总是一个(性能)错误。
为什么LinkedList
糟糕:
ArrayList
时慢。 ArrayList
相同,反正它可能会明显变慢。 LinkedList
是很令人震惊的,因为它可能是错误的选择。