数组列表的容量和数组大小的区别

Distinction between the capacity of an array list and the size of an array

我在核心阅读了以下片段 Java 我预订了。

Allocating an array list as new ArrayList <'Employee>(100) // capacity is 100

is not the same as allocating a new array as new Employee[100] // size is 100

There is an important distinction between the capacity of an array list and the size of an array. If you allocate an array with 100 entries, then the array has 100 slots, ready for use. An array list with a capacity of 100 elements has the potential of holding 100 elements (and, in fact, more than 100, at the cost of additional reallocations); but at the beginning, even after its initial construction, an array list holds no elements at all.

当我看到源代码数组列表时,构造函数创建了一个给定容量的对象数组,该数组已准备好容纳给定容量的元素(下面是代码片段)。

public ArrayList(int initialCapacity) {
     super();
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal Capacity: "+
                                            initialCapacity);
     this.elementData = new Object[initialCapacity];
 }

我无法弄清楚作者在上面的文字中提到的实际差异。

数组有长度,在创建时指定,不能更改。

如果您创建一个新数组 myArray = new Object[100],那么您可以从 myArray[0] 读取和写入 myArray[99](您会发现它充满了 null)。

另一方面,列表有一个 size(),它从零开始,并在您添加项目时增长。列表的 size() 跟踪您实际放入了多少东西,而不是它有多少 space。

如果您使用 myList = new ArrayList(100) 创建一个列表,然后尝试使用 getset 任何元素,您将得到一个 IndexOutOfBoundsException,因为该列表是空,直到你 add 给它一些东西。

总而言之,大小为 100 的数组最初将包含 100 个空值,但列表将为空。

如果您使用 arr = new Employee[100] 分配一个新数组,该数组 (arr.length) 的大小将为 100。它有 100 个元素。所有元素最初都是空的(因为这是一个对象引用数组),但仍然有 100 个元素。

如果您执行类似 list = new ArrayList <Employee>(100) 的操作并尝试检查 list.size(),您将得到 0。列表中没有元素。

在内部,ArrayList确实在需要扩展其容量之前分配了足够的空间来放置 100 个项目,但这是一个内部实现细节,列表将其内容呈现给您 "no items stored".仅当您实际执行 list.add(something) 时,列表中才会有项目。

因此,尽管列表预先分配了存储空间,但它与程序通信的 API 告诉您其中没有项目。其内部数组中的空项对您不可用 - 您无法检索或更改它们。

这似乎措辞不当,如果我理解不正确,可能会不正确。

我相信它想说的是 ArrayList 的初始容量和 ArrayList 的初始大小之间存在差异。

List<Employee> employees = new ArrayList<>(100);
int size = employes.size();

size 初始容量为 100 时将为 0。

您阅读源代码的方式是正确的。

ArrayList只是表示抽象列表的一种方式,ArrayList的容量是系统如何实现逻辑列表的实现细节。

一个ArrayList用一个实际的数组来存储列表的元素"under the covers."数组在计算机内存中的实际实现在分配时是有一定大小的;这个大小就是 ArrayList 的容量。 ArrayList 除了存储定长数组外,还通过存储列表的逻辑长度来模拟可变大小的列表。因此,如果您有一个容量为 10 且包含 4 个逻辑元素的 ArrayList,则 ArrayList 可以表示为一个长度和一个数组

(4) | e1 | e2 | e3 | e4 | __ | __ | __| __ | __ | __ |

其中 (4) 是列表的逻辑长度,'__' 表示被忽略的数据,因为它不是逻辑列表的一部分。如果您尝试访问此 ArrayList 的第 5 个元素,它将抛出异常,因为它知道第 5 个元素尚未初始化。如果我们然后将一个额外的元素 e5 添加到列表中,则 ArrayList 变为

(5) | e1 | e2 | e3 | e4 | e5 | __ | __ | __ | __ | __ |

请注意,容量没有变化,而逻辑长度有变化,因为底层数组仍然能够处理逻辑列表中的所有数据。

如果您设法向该列表中添加十个以上的元素,ArrayList 将不会中断。 ArrayList 是一种抽象,旨在与所有数组操作兼容。相反,当 ArrayList 的逻辑长度超过其原始容量时,它会更改其容量。如果我们将元素 (a1, a2, ..., a7) 添加到上面的列表中,生成的 ArrayList 可能看起来像

(12) | e1 | e2 | e3 | e4 | e5 |一个1 |一个2 |一个3 |一个4 | a5 |一个6 |一个7 | __ | __ | __ | __ | __ | __ | __ | __ |

容量为 20。

一旦创建了ArrayList,后面的所有编程都可以忽略容量;逻辑不受影响。但是,系统在某些操作下的性能可能会受到影响。例如,增加容量可能涉及分配更大的数组,将第一个数组复制到第二个数组,然后执行操作。与例如相比,这可能非常慢链表上的相同操作。因此,选择 ArrayList 的容量大于或至少与实际运行时环境中预期的实际元素数相当是明智的。

区别在于固定大小容器(数据结构)和可变大小容器。

一个数组是一个固定大小的容器,它容纳的元素个数是在创建数组时确定的,永远不会改变。 (创建数组时,所有这些元素都将具有一些默认值,例如,引用类型为 null 或整数为 0,但它们都将存在于数组中:您可以为每个元素建立索引。)

一个list是一个可变大小的容器,里面的元素个数可以变化,从0到你的数量不等想要(受执行限制)。创建后,元素的数量可以增加或减少。您可以随时通过索引检索任何元素。

但是Java概念List实际上是一个接口,可以用很多不同的方式实现。因此,ArrayListLinkedList 等。有一个数据结构 "behind" 列表来实际保存元素。并且该数据结构本身可能是固定大小或可变大小,并且在任何给定时间可能具有列表中元素数量的确切大小,或者它可能有一些 extra "buffer" space.

例如,

LinkedList 在其基础数据结构中始终具有与它所代表的列表中完全相同数量的 "places for elements"。但是 ArrayList 使用固定长度的数组作为其后备存储。

对于 ArrayList,在任何给定时间,列表中的元素数可能与其后面的数组可以容纳的元素数不同。元素的 "extra" 位置仅包含空值或 0 或其他任何内容,但 ArrayList 永远不会让您访问这些位置。当您向 ArrayList 添加元素时,它们会在底层数组中占据更多位置,直到底层数组最终已满。添加到 ArrayListnext 元素会导致分配一个全新的固定大小数组 - 比 "current" 数组稍大 - 以及所有列表元素复制到它(原始数组被丢弃)。为了防止这种昂贵的操作(分配和复制)过于频繁地发生,新数组比当前数组大(某种因素),因此具有当时不会保存列表元素的元素 - 它们是空的(null或 0).

因此,由于所表示的列表中的元素数量与实现数据结构可以容纳的元素数量之间(可能)存在差异,因此有 两个有效概念。

列表的大小是其中的元素个数。列表的容量是后备数据结构此时可以容纳的元素数量。大小将随着元素添加到列表或从列表中删除而改变。当您使用的列表的实现需要它时,容量会发生变化。 (当然,大小永远不会大于容量。)

(顺便说一句,对于固定大小的容器,size 经常被称为 length,因此数组有一个 属性 length 和字符串有一个方法 length()。不同的语言 - 有时甚至是相同的语言 - 使用 "size" 和 "length" 不一致,但它们总是表示 size,术语 "capacity" 始终用于底层数据结构的 size/length。)

让我们举一个现实生活中的例子。 考虑一辆十八人座的公共汽车,可容纳十八名乘客。任何给定时间的乘客人数都可以少于 18 人,但不能多于 18 人。当乘客人数为18人时,无法容纳其他乘客。

在 ArrayList 中,容量与我们的总线容量有一些共同之处,因为它定义了可以容纳的元素数量。然而,与我们的总线不同,容量会扩展以容纳元素数量,直到 Integer.MAX_VALUE.

大小也是一样,就像我们的总线一样,列表中元素的大小不能超过容量。试想一下,当 50 名乘客乘坐一辆 18 座的公共汽车时!你肯定不想坐那辆公共汽车。

  1. A​​rrayList 是 List 接口的动态数组实现。 添加时,我们不必担心 ArrayList 的大小 它的元素。
  2. 它会随着我们向其中添加元素并调整基础数组的大小而自动增长 因此。
  3. 这个内部数组的大小就是ArrayList的容量。
  4. 当内部数组已满,我们尝试向 ArrayList,创建一个具有更多容量的新数组,并且所有现有的数组项都是 复制到它。

示例: ArrayList aListNumbers = new ArrayList(20);

将创建一个初始容量为 20 的 ArrayList 对象。 这意味着 ArrayList 在需要调整内部数组大小之前将能够容纳 20 个元素。