【问题标题】:How to define the concept of capacity in ArrayLists?ArrayLists中如何定义容量的概念?
【发布时间】:2011-03-23 20:17:43
【问题描述】:

我了解容量是 ArrayList 中可能包含也可能不包含引用对象的值的元素或可用空间的数量。我正在尝试更多地了解容量的概念。

所以我有三个问题:

1) 从内存的角度定义容量代表什么的好方法有哪些?

...分配给 ArrayList 的(连续?)内存?

...ArrayLists 在(堆?)上的内存占用?

2) 如果上述情况属实,那么改变容量需要某种方式的内存管理开销?

3) 有人举过 #2 是或可能是性能问题的例子吗?除了可能不断调整容量的大量大型 ArrayList 之外?

【问题讨论】:

  • 如果你知道 C,你会做内存分配之类的事情,但是是的,它们会随着内存的动态增长而重新分配内存(我认为是 1.75 或其他因素)总是最好在知道大小的情况下进行实例化,否则为您在 3 中说过,当您添加大量数据时,它会分配大量空白空间。我会让更了解内存分配以及Java如何提供真正答案的人提供真正的答案
  • (我认为是 1.75 或其他因素) 公式是 int newCapacity = (oldCapacity * 3)/2 + 1;

标签: java arraylist capacity


【解决方案1】:
  1. 该类称为 ArrayList,因为它基于数组。容量是数组的大小,需要一块连续的堆内存。但是请注意,数组本身仅包含对元素的引用,这些元素是堆上的独立对象。
  2. 增加容量需要分配一个更大的新数组,并将旧数组中的所有引用复制到新数组,之后旧数组就可以进行垃圾回收了。
  3. 您已经列举了性能可能成为问题的主要案例。在实践中,我从未见过它真正成为问题,因为元素对象通常比列表占用更多的内存(可能还有 CPU 时间)。

【讨论】:

    【解决方案2】:

    ArrayList 是这样实现的:

    class ArrayList {
      private Object[] elements;
    }
    

    容量是该数组的大小。

    现在,如果你的容量是 10,并且你要添加第 11 个元素,ArrayList 会这样做:

    Object[] newElements = new Object[capacity * 1.5];
    System.arraycopy(this.elements, newElements);
    this.elements = newElements;
    

    因此,如果您从小容量开始,ArrayList 最终会在您不断添加元素时创建一堆数组并为您复制东西,这不好。

    另一方面,如果你指定容量为 1,000,000 并且只向 ArrayList 添加 3 个元素,那也有点糟糕。

    经验法则:如果您知道容量,请指定它。如果您不确定但知道上限,请指定。如果您不确定,请使用默认值。

    【讨论】:

      【解决方案3】:

      容量正如您所描述的那样 - 分配给 ArrayList 用于存储值的连续内存。 ArrayList 将所有值存储在一个数组中,并自动为您调整数组的大小。这会在调整大小时产生内存管理开销。

      如果我没记错的话,当您尝试添加一个超出容量的元素时,Java 会将 ArrayList 的后备数组的大小从 N 增加到 2N + 2。我不知道当您使用insert 方法(或类似方法)在超出容量末尾的特定位置插入时它会增加多少大小,甚至它是否允许这样做。

      这里有一个例子可以帮助你思考它是如何工作的。将|s 之间的每个空格想象成后备数组中的一个单元格:

      | | |
      

      size = 0(不包含元素),容量 = 2(可以包含 2 个元素)。

      |1| |
      

      size = 1(包含 1 个元素),容量 = 2(可以包含 2 个元素)。

      |1|2|
      

      size = 2, capacity = 2. 添加另一个元素:

      |1|2|3| | | |
      

      大小增加 1,容量增加至 6 (2 * 2 + 2)。对于大型数组,这可能会很昂贵,因为分配一个大的连续内存区域可能需要一些工作(与分配许多小块内存的 LinkedList 不同),因为 JVM 需要搜索适当的位置,并且可能需要要求操作系统提供更多内存。将大量值从一个地方复制到另一个地方也很昂贵,一旦找到这样的区域就会这样做。

      我的经验法则是:如果您知道您需要的容量,请使用 ArrayList,因为只有一次分配并且访问速度非常快。如果您不知道所需的容量,请使用 LinkedList,因为添加新值始终需要相同的工作量,并且不涉及复制。

      【讨论】:

      • 因为链表的每个新元素都会创建一个新的链表节点,所以向这样的链表添加元素的开销是恒定的,平均而言可能比在数组链表的末尾添加一个元素更大,甚至包括再生数组(因为这些很少发生)。仅当我想在前面(或在迭代器找到的位置)添加元素时,或者如果您确实需要保证 O(1) 来添加元素,而不仅仅是摊销 O(1),我才会使用链表。
      • @Paŭlo 是的,但我仍然更喜欢它们。如果你想要排队,他们真的是唯一的出路。
      • @Paŭlo Ebermann ...你知道,我刚刚在 Murach 的 Java SE6 中读到了相反的内容。 Murach 指出,由于 LinkedList 不使用数组,因此在 LinkedList 中插入元素会更有效,因为只需要更改前面和后面的指针,而不是像 ArrayList 那样创建一个全新的数组。也许你正在考虑访问一个需要大量开销的元素,因为必须逐步执行每个元素的指针才能到达目的地。
      • @Cephi:当然,如果您有一个 ArrayList 在其容量范围内(即内部数组中没有更多可用空间),则添加一个元素比在链表中花费更多的时间。但是,如果您将 n 元素添加到此类列表(最初为空),则此类调整大小和复制仅发生 O(log(n)) 次(并且复制少于 2·n 数组条目),而您仍然需要创建 @987654330 @new 列表节点对象并设置3·n 指针。 (当然,这是为了在末尾添加元素。)
      • @Cephi:如果在开头添加到列表中,则链表具有明显的优势,因为在 ArrayList 中每个元素我们必须将所有后续元素向后移动,而在链表的情况下,我们只需要创建一个节点对象并设置/更改4 指针。
      【解决方案4】:

      1) 从内存的角度定义容量代表什么的好方法有哪些?

      ...分配给 ArrayList 的(连续?)内存?

      是的,ArrayList 是由一个数组备份的,它代表了内部数组的大小。

      ...ArrayLists 在(堆?)上的内存占用?

      是的,数组容量越大,arraylist占用的空间越多。

      2) 如果上述情况属实,那么改变容量需要某种方式的内存管理开销?

      是的。当列表变得足够大时,分配一个更大的数组并复制内容。之前的数组可能会被丢弃并标记为垃圾回收。

      3) 任何人都有一个例子,其中 #2 是或可能是性能问题?除了可能不断调整容量的大量大型 ArrayList 之外?

      是的,如果您创建初始容量为 1 的 ArrayList(例如),并且您的列表超出此范围。如果您预先知道要存储的元素数量,则最好请求该大小的初始容量。

      但是我认为这在您的优先级列表中应该是较低的,虽然数组复制可能经常发生,但它在 Java 的早期阶段就已经优化,不应该成为问题。我认为最好选择一个正确的算法。记住:Premature optimization is the root of all evil

      另见:When to use LinkedList over ArrayList

      【讨论】:

        猜你喜欢
        • 2019-11-06
        • 1970-01-01
        • 1970-01-01
        • 2021-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多