【问题标题】:Why Java ArrayLists do not shrink automatically为什么 Java ArrayLists 不会自动收缩
【发布时间】:2017-01-30 10:32:46
【问题描述】:

很久以前,我看了一个普林斯顿 Coursera MOOC 的视频讲座:算法介绍,可以在 here 找到。它解释了在添加或删除元素时调整 ArrayList 类似结构的成本。事实证明,如果我们想为我们的数据结构提供调整大小,我们将从O(n) 变为amortized O(n) 以进行addremove 操作。

我已经使用 Java ArrayList 好几年了。我一直确信它们会自动增长和收缩。直到最近,令我大吃一惊的是,我在this post 中被证明是错误的。 Java ArrayLists 不会自动收缩(尽管它们当然会增长)。

这是我的问题:

  1. 在我看来,在ArrayLists 中提供收缩不会造成任何伤害,因为性能已经是amortized O(n)。为什么 Java 的创造者没有在设计中包含这个特性?

  2. 我知道像HashMaps 这样的其他数据结构也不会自动收缩。 Java中是否还有其他构建在支持自动收缩的数组之上的数据结构?

  3. 其他语言的趋势是什么?在 Python/C# 中的列表、字典、地图、集合等情况下,自动收缩看起来如何。如果它们与 Java 的做法相反,那么我的问题是:为什么?

【问题讨论】:

  • 容器缩小的缺点是,如果添加更多元素,您将不得不再次增长它。仅仅因为您从列表中删除了元素,库不能假设您仍然不需要空间。我不知道像ArrayList 这样会自动缩小的数组包装器,如果发现有人这样做,我会感到非常惊讶。如果您作为ArrayList 用户知道您的列表容量现在可以减少,您可以使用trimToSize()
  • 我知道还有更多工作要做,但在O 术语中,如果您提供自动缩小功能,您将失去“任何东西”,就像视频中展示的那样。 addremove 的复杂度仍然是 amortized O(n)
  • 顺便说一句,如果有人给-1,我很乐意阅读建设性反馈。
  • 自动增长是必需的以适应更多元素。正如 khelwood 所说,自动收缩不是必需的,如果需要可以手动完成。说“你什么都没有”并不完全正确。您增加了复杂性,并且您无法确定是否需要自动收缩。
  • 除非 JDK 开发人员判断 ArrayList 操作中的可预测性优于 System.arrayCopy 中的偶尔打嗝或自定义汇编代码,速度如此之快以至于它会自动调整大小 - 我不能说我看到好处 a) 每次我们调用 remove() 时支付保证 O(N) 和 b) 如果/在需要时手动修剪列表,后者尤其令人讨厌。这绝对是一个错误的感觉。冒险猜测一下,我会说他们选择了 a),但老实说,如果您那么关心性能,那么无论如何您都应该直接使用数组。

标签: java arraylist resize


【解决方案1】:

cmets 已经涵盖了您所询问的大部分内容。以下是对您的问题的一些想法:

  1. 在 Java 中创建像 ArrayList 这样的结构时,开发人员会根据运行时/性能做出某些决定。他们显然决定从“正常”操作中排除收缩以避免额外的运行时间,这是必需的。

  2. 问题是为什么要自动收缩。 ArrayList 并没有增长那么多(确切地说,系数约为 1.5;newCapacity = oldCapacity + (oldCapacity >> 1))。也许您还可以在中间插入,而不仅仅是在末尾附加。那么LinkedList(不基于数组-> 不需要收缩)可能会更好。这实际上取决于您的用例。如果你认为你真的需要 ArrayList 所做的一切,但在删除元素时它必须缩小(我怀疑你真的需要这个),只需扩展 ArrayList 并覆盖这些方法。不过要小心!如果每次删除都缩水,那么你又回到了O(n)

  3. C# List 和 C++ vector 在删除元素时收缩列表的行为相同。但自动增长的因素各不相同。甚至一些 Java 实现也会使用不同的因素。

【讨论】:

    【解决方案2】:

    自动收缩的另一个问题是,您可能会遇到非常可怕的“病态”情况,每次插入和删除列表都会导致后备数组的增长或收缩。

    例如,如果后备数组的初始容量为 10,这样数组会在插入第 11 个元素时增长(容量将增长到 15),一个自然的实现是在列表大小后缩小后备数组降至 11 以下。如果您有一个长度在 10 到 11 之间变化的列表,那么您将不断更改支持数组。这不仅会增加运行时开销,而且如果每次操作都会导致 10 或 15 个对象变成垃圾,您可能会开始对垃圾收集器施加很大压力。

    【讨论】:

    • Robert Sedgewick(我提到的课程的教授)实际上为这个问题提供了解决方案。在您的特定情况下,您可以在将第 11 个元素添加到数组时增加大小。仅当(例如)数组中的元素少于 6 个时,它才会缩小。这不会使整体大 O 复杂性恶化。当我问我的问题时,我认为应该实施这些技术。
    • @GA1 - 首先,虽然根据算法的大 O 复杂度对算法进行分类很重要(并且,在一般情况下,选择具有最小复杂度的算法),因为两种算法具有相同的大 O 复杂度并不意味着它们具有相同的运行时间。运行时间也不是唯一的考虑因素(空间或内存使用是另一个关键因素)。因此,如果将数组用作 6 到 11 个元素之间的堆栈,则将数组增长到 11(容量为 15)并缩小到 6(容量为 10)的数组列表可能会很快耗尽系统中的所有内存。
    • @GA1 - 有点幽默,两个按小时支付的人都赚取 O(n),其中 n 是他们工作的小时数,但我宁愿获得 100.00 / 小时的报酬,而不是 5.00。
    【解决方案3】:

    虽然arraylist的收缩仍然是摊销O(n)时间复杂度,但它涉及到更多的操作。

    通过缩小你只是通过添加一些计算来节省一些内存空间,这不是一个明智的决定,因为摩尔定律说计算机空间每 2 年翻一番。所以时间在算法上比空间更有价值。

    【讨论】:

      猜你喜欢
      • 2014-10-01
      • 1970-01-01
      • 2020-01-04
      • 1970-01-01
      • 2020-04-28
      • 1970-01-01
      • 2017-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多