何时通过ArrayList使用LinkedList?

我一直只使用一个:

List<String> names = new ArrayList<>();

我使用接口作为可移植性的类型名称,以便当我提出这些问题时,我可以重写我的代码。

什么时候应该将LinkedList用于ArrayList ,反之亦然?


总结 ArrayListArrayDeque在比LinkedList更多的用例中是可取的。 不确定 - 只需从ArrayList开始。


LinkedListArrayList是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


    到目前为止,除了普遍认为LinkedListArrayList “多得多”之外,似乎没有人会解决这些列表中的每个列表的内存占用情况,所以我做了一些数字处理以示范两个列表占用多少N空引用。

    由于引用在其相关系统上是32位或64位(即使为空),我已经为32位和64位LinkedListsArrayLists包含了4组数据。

    注意: ArrayList行显示的大小是用于修剪列表的 - 实际上, ArrayList支持数组的容量通常大于其当前元素数。

    注2:(感谢BeeOnRope)由于CompressedOops现在默认从JDK6开始,所以64位机器的值将基本上与32位对应的值匹配,除非您明确关闭它。


    LinkedList和ArrayList图的元素数×字节数


    结果清楚地表明, LinkedListArrayList多得多,特别是在元素数量非常高的情况下。 如果记忆是一个因素,请避开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糟糕:

  • 它使用大量小内存对象,因此会影响整个过程的性能。
  • 很多小对象对缓存局部性不利。
  • 任何索引操作都需要遍历,即具有O(n)性能。 这在源代码中并不明显,导致算法O(n)比使用ArrayList时慢。
  • 获得好的表现是棘手的。
  • 即使big-O性能与ArrayList相同,反正它可能会明显变慢。
  • 在源代码中看到LinkedList是很令人震惊的,因为它可能是错误的选择。
  • 链接地址: http://www.djcxy.com/p/445.html

    上一篇: When to use LinkedList over ArrayList?

    下一篇: Creating a memory leak with Java